// ============================================================================
// Charts — pure SVG, no chart lib. Equity curve, drawdown, bar charts, exposure.
// ============================================================================

const useSize = () => {
  const ref = React.useRef(null);
  const [size, setSize] = React.useState({ w: 0, h: 0 });
  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => {
      setSize({ w: e.contentRect.width, h: e.contentRect.height });
    });
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, []);
  return [ref, size];
};

const EquityChart = ({ points, height = 180, scale = "linear" }) => {
  const [ref, { w }] = useSize();
  const W = w || 600, H = height, PAD = { l: 44, r: 8, t: 8, b: 22 };
  const safePoints = points && points.length ? points : [{ i: 0, v: 0 }, { i: 1, v: 0 }];
  const xs = safePoints.map((p, i) => p.i ?? i);
  const values = safePoints.map((p) => Number(p.v) || 0);
  const toYDomain = (v) => scale === "log" ? Math.log10(Math.max(v, 1e-9)) : v;
  const fromYDomain = (v) => scale === "log" ? Math.pow(10, v) : v;
  const ys = values.map(toYDomain);
  const xMin = Math.min(...xs), xMax = Math.max(...xs);
  const yMin = Math.min(...ys), yMax = Math.max(...ys);
  const x = (v) => PAD.l + ((v - xMin) / (xMax - xMin || 1)) * (W - PAD.l - PAD.r);
  const y = (v) => PAD.t + (1 - (toYDomain(v) - yMin) / (yMax - yMin || 1)) * (H - PAD.t - PAD.b);
  const path = safePoints.map((p, i) => `${i === 0 ? "M" : "L"} ${x(p.i ?? i)} ${y(p.v)}`).join(" ");
  const area = `${path} L ${x(xMax)} ${H - PAD.b} L ${x(xMin)} ${H - PAD.b} Z`;
  const ticks = 4;
  const yTicks = Array.from({ length: ticks + 1 }, (_, i) => fromYDomain(yMin + (yMax - yMin) * (i / ticks)));

  return (
    <div ref={ref} className="tb-chart">
      <svg width={W} height={H}>
        <defs>
          <linearGradient id="eq-grad" x1="0" x2="0" y1="0" y2="1">
            <stop offset="0%" stopColor="var(--accent)" stopOpacity="0.32" />
            <stop offset="100%" stopColor="var(--accent)" stopOpacity="0" />
          </linearGradient>
        </defs>
        {yTicks.map((t, i) => (
          <g key={i}>
            <line x1={PAD.l} x2={W - PAD.r} y1={y(t)} y2={y(t)} stroke="var(--chart-grid)" />
            <text x={PAD.l - 6} y={y(t)} dy="3" textAnchor="end" fill="var(--fg-3)" fontSize="9" fontFamily="var(--font-mono)">
              {fmt.abbr(t, 1)}
            </text>
          </g>
        ))}
        <path d={area} fill="url(#eq-grad)" />
        <path d={path} stroke="var(--accent)" strokeWidth="1.5" fill="none" />
        <circle cx={x(xMax)} cy={y(values[values.length - 1])} r="3" fill="var(--accent)" />
      </svg>
    </div>
  );
};

const DrawdownChart = ({ points, height = 110 }) => {
  const [ref, { w }] = useSize();
  const W = w || 600, H = height, PAD = { l: 44, r: 8, t: 8, b: 18 };
  const safePoints = points && points.length ? points : [{ i: 0, v: 0 }, { i: 1, v: 0 }];
  // Drawdown is measured from the latest high-water mark, so every new high
  // resets the current drawdown to 0%.
  let peak = -Infinity;
  const dd = safePoints.map((p, i) => {
    const value = Number(p.v) || 0;
    if (value >= peak) {
      peak = value;
      return { i, v: 0 };
    }
    return { i, v: peak > 0 ? ((value - peak) / peak) * 100 : 0 };
  });
  const ys = dd.map((p) => p.v);
  const yMin = Math.min(...ys, 0);
  const x = (v) => PAD.l + (v / (safePoints.length - 1 || 1)) * (W - PAD.l - PAD.r);
  const y = (v) => PAD.t + (1 - (v - yMin) / (0 - yMin || 1)) * (H - PAD.t - PAD.b);
  const path = dd.map((p, i) => `${i === 0 ? "M" : "L"} ${x(p.i)} ${y(p.v)}`).join(" ");
  const area = `${path} L ${x(safePoints.length - 1)} ${y(0)} L ${x(0)} ${y(0)} Z`;
  return (
    <div ref={ref} className="tb-chart">
      <svg width={W} height={H}>
        <defs>
          <linearGradient id="dd-grad" x1="0" x2="0" y1="1" y2="0">
            <stop offset="0%" stopColor="var(--danger)" stopOpacity="0.30" />
            <stop offset="100%" stopColor="var(--danger)" stopOpacity="0" />
          </linearGradient>
        </defs>
        <line x1={PAD.l} x2={W - PAD.r} y1={y(0)} y2={y(0)} stroke="var(--chart-grid)" />
        <text x={PAD.l - 6} y={y(0)} dy="3" textAnchor="end" fill="var(--fg-3)" fontSize="9" fontFamily="var(--font-mono)">0%</text>
        <text x={PAD.l - 6} y={y(yMin)} dy="3" textAnchor="end" fill="var(--fg-3)" fontSize="9" fontFamily="var(--font-mono)">{yMin.toFixed(1)}%</text>
        <path d={area} fill="url(#dd-grad)" />
        <path d={path} stroke="var(--danger)" strokeWidth="1.5" fill="none" />
      </svg>
    </div>
  );
};

const BarChartH = ({ data, valueKey = "pnl", labelKey = "symbol", height = 180 }) => {
  const [ref, { w }] = useSize();
  const W = w || 400, H = height, PAD = { l: 70, r: 40, t: 6, b: 6 };
  const safeData = data && data.length ? data : [];
  const max = Math.max(1, ...safeData.map((d) => Math.abs(Number(d[valueKey]) || 0)));
  const rowH = safeData.length ? (H - PAD.t - PAD.b) / safeData.length : H - PAD.t - PAD.b;
  const barH = Math.min(20, rowH - 6);
  const zeroX = PAD.l + 4;
  const usable = W - PAD.l - PAD.r - 8;
  return (
    <div ref={ref} className="tb-chart">
      <svg width={W} height={H}>
        {!safeData.length && (
          <text x={W / 2} y={H / 2} textAnchor="middle" fill="var(--fg-3)" fontSize="11" fontFamily="var(--font-mono)">
            선택 구간 데이터 없음
          </text>
        )}
        {safeData.map((d, i) => {
          const v = Number(d[valueKey]) || 0;
          const isPos = v >= 0;
          const len = (Math.abs(v) / max) * usable;
          const yy = PAD.t + i * rowH + (rowH - barH) / 2;
          return (
            <g key={i}>
              <text x={PAD.l - 6} y={yy + barH / 2} dy="3.5" textAnchor="end" fill="var(--fg-2)" fontSize="11" fontFamily="var(--font-mono)">
                {d[labelKey]}
              </text>
              <rect x={zeroX} y={yy} width={len} height={barH} rx="2" fill={isPos ? "var(--success)" : "var(--danger)"} opacity="0.85" />
              <text x={zeroX + len + 6} y={yy + barH / 2} dy="3.5" fill={isPos ? "var(--success-fg)" : "var(--danger-fg)"} fontSize="10" fontFamily="var(--font-mono)" fontWeight="500">
                {fmt.abbr(v, 2)}
              </text>
            </g>
          );
        })}
      </svg>
    </div>
  );
};

const MonthlyBars = ({ data, height = 130 }) => {
  const [ref, { w }] = useSize();
  const W = w || 400, H = height, PAD = { l: 30, r: 8, t: 8, b: 20 };
  const safeData = data && data.length ? data : [{ m: "—", v: 0 }];
  const max = Math.max(1, ...safeData.map((d) => Math.abs(d.v)));
  const bw = (W - PAD.l - PAD.r) / safeData.length;
  const zeroY = PAD.t + (H - PAD.t - PAD.b) / 2;
  const half = (H - PAD.t - PAD.b) / 2;
  return (
    <div ref={ref} className="tb-chart">
      <svg width={W} height={H}>
        <line x1={PAD.l} x2={W - PAD.r} y1={zeroY} y2={zeroY} stroke="var(--chart-grid)" />
        {safeData.map((d, i) => {
          const isPos = d.v >= 0;
          const h = (Math.abs(d.v) / max) * half;
          const x = PAD.l + i * bw + bw * 0.15;
          const w_ = bw * 0.7;
          return (
            <g key={i}>
              <rect x={x} y={isPos ? zeroY - h : zeroY} width={w_} height={h} rx="2" fill={isPos ? "var(--success)" : "var(--danger)"} opacity="0.85" />
              <text x={x + w_ / 2} y={H - 6} textAnchor="middle" fill="var(--fg-3)" fontSize="10" fontFamily="var(--font-mono)">{d.m}</text>
            </g>
          );
        })}
      </svg>
    </div>
  );
};

const ExposureBar = ({ data }) => {
  const colors = ["var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)"];
  return (
    <div className="tb-exposure">
      <div className="tb-exposure-bar">
        {data.map((d, i) => (
          <span key={i} style={{ width: `${d.pct}%`, background: colors[i % colors.length] }} title={`${d.symbol} ${d.pct}%`} />
        ))}
      </div>
      <div className="tb-exposure-legend">
        {data.map((d, i) => (
          <div key={i} className="tb-exposure-leg">
            <span className="tb-exposure-swatch" style={{ background: colors[i % colors.length] }} />
            <span className="mono tb-strong">{d.symbol}</span>
            <span className="mono tb-meta">{d.pct}%</span>
          </div>
        ))}
      </div>
    </div>
  );
};

const SymbolPriceChart = ({
  symbol,
  bars = [],
  openPositions = [],
  closedTrades = [],
  height = 360,
  focusTicket = null,
  onHoverTicketChange,
  onTicketClick,
}) => {
  const [ref, { w }] = useSize();
  const [localHoverTicket, setLocalHoverTicket] = React.useState(null);
  const activeTicket = focusTicket ?? localHoverTicket;
  const setHoverTicket = (ticket) => {
    setLocalHoverTicket(ticket);
    onHoverTicketChange?.(ticket);
  };
  const W = Math.max(w || 900, 760), H = height, PAD = { l: 62, r: 18, t: 14, b: 28 };
  const safeBars = (bars || []).filter((b) => b && Number.isFinite(Number(b.close)));
  if (!safeBars.length) {
    return (
      <div className="tb-chart tb-empty-chart" style={{ height }}>
        <div>
          <div className="label-caps">차트 데이터 없음</div>
          <div className="tb-meta">DB market_bars_5m에 {symbol || "선택 종목"} 데이터가 아직 없습니다.</div>
        </div>
      </div>
    );
  }

  const tsOf = (v) => {
    const t = v ? new Date(v).getTime() : NaN;
    return Number.isFinite(t) ? t : null;
  };
  const times = safeBars.map((b, i) => tsOf(b.ts) ?? i);
  const xMin = times[0], xMax = times[times.length - 1];
  const lastClose = Number(safeBars[safeBars.length - 1]?.close) || 0;

  const tradeLines = [
    ...(closedTrades || []).map((p) => ({
      kind: "closed",
      ticket: p.ticket,
      direction: p.direction,
      startTs: tsOf(p.openedAt),
      endTs: tsOf(p.closedAt),
      entry: Number(p.entry),
      exit: Number(p.exit || p.entry),
      pnl: Number(p.pnl) || 0,
      lots: Number(p.lots) || 0,
      source: p.source || "",
    })),
    ...(openPositions || []).map((p) => ({
      kind: "open",
      ticket: p.ticket,
      direction: p.direction,
      startTs: tsOf(p.openedAt),
      endTs: xMax,
      entry: Number(p.entry),
      exit: Number(p.current || lastClose || p.entry),
      sl: Number(p.sl),
      tp: Number(p.tp),
      pnl: Number(p.pnl) || 0,
      lots: Number(p.lots) || 0,
      source: p.source || "",
    })),
  ].filter((p) => p.startTs != null && p.endTs != null && Number.isFinite(p.entry) && Number.isFinite(p.exit) && p.entry > 0 && p.exit > 0)
    .map((p) => ({
      ...p,
      visibleStartTs: Math.max(xMin, Math.min(xMax, p.startTs)),
      visibleEndTs: Math.max(xMin, Math.min(xMax, p.endTs)),
      startVisible: p.startTs >= xMin && p.startTs <= xMax,
      endVisible: p.endTs >= xMin && p.endTs <= xMax,
    }))
    .filter((p) => p.visibleEndTs >= xMin && p.visibleStartTs <= xMax && Math.abs(p.visibleEndTs - p.visibleStartTs) > 1);

  const markerRows = tradeLines.flatMap((p) => [
    p.startVisible ? {
      kind: p.kind === "open" ? "open-entry" : "closed-entry",
      ts: p.startTs,
      price: p.entry,
      ticket: p.ticket,
      label: `${p.kind === "open" ? "오픈" : "청산"} ${p.direction === "short" ? "S" : "L"} 진입 #${p.ticket}`,
      tone: p.direction === "short" ? "danger" : "success",
      detail: `${fmt.num(p.lots, 2)} lots · ${p.source}`,
    } : null,
    p.kind === "closed" && p.endVisible ? {
      kind: "closed",
      ts: p.endTs,
      price: p.exit,
      ticket: p.ticket,
      label: `청산 #${p.ticket}`,
      tone: p.pnl >= 0 ? "success" : "danger",
      detail: `${fmt.pnl(p.pnl, 2)} · 청산가`,
    } : null,
  ]).filter(Boolean);

  const highs = safeBars.map((b) => Number(b.high || b.close)).filter(Number.isFinite);
  const lows = safeBars.map((b) => Number(b.low || b.close)).filter(Number.isFinite);
  const tradePrices = [
    ...tradeLines.flatMap((m) => [m.entry, m.exit, m.sl, m.tp]),
  ].filter((v) => Number.isFinite(v) && v > 0);
  let yMin = Math.min(...lows, ...tradePrices);
  let yMax = Math.max(...highs, ...tradePrices);
  const padY = (yMax - yMin || Math.max(1, yMax * 0.001)) * 0.08;
  yMin -= padY;
  yMax += padY;

  const x = (t) => PAD.l + ((t - xMin) / (xMax - xMin || 1)) * (W - PAD.l - PAD.r);
  const y = (v) => PAD.t + (1 - (v - yMin) / (yMax - yMin || 1)) * (H - PAD.t - PAD.b);
  const yTicks = Array.from({ length: 5 }, (_, i) => yMin + (yMax - yMin) * (i / 4));
  const xTicks = [0, 0.25, 0.5, 0.75, 1].map((r) => xMin + (xMax - xMin) * r);
  const decimals = symbol === "USDJPY" ? 3 : ["XAUUSD", "NAS100"].includes(symbol) ? 2 : 5;
  const candleW = Math.max(2, Math.min(9, ((W - PAD.l - PAD.r) / safeBars.length) * 0.62));
  const hoverKey = activeTicket == null ? null : String(activeTicket);
  const isHovered = (ticket) => hoverKey != null && String(ticket) === hoverKey;
  const isDimmed = (ticket) => hoverKey != null && !isHovered(ticket);
  const orderByHover = (rows) => hoverKey
    ? [...rows.filter((r) => !isHovered(r.ticket)), ...rows.filter((r) => isHovered(r.ticket))]
    : rows;
  const orderedTradeLines = orderByHover(tradeLines);
  const orderedMarkers = orderByHover(markerRows);

  return (
    <div ref={ref} className={`tb-chart tb-price-chart ${hoverKey ? "tb-chart-focus-active" : ""}`}>
      <svg width={W} height={H}>
        {yTicks.map((t, i) => (
          <g key={i}>
            <line x1={PAD.l} x2={W - PAD.r} y1={y(t)} y2={y(t)} stroke="var(--chart-grid)" />
            <text x={PAD.l - 8} y={y(t)} dy="3.5" textAnchor="end" fill="var(--fg-3)" fontSize="9" fontFamily="var(--font-mono)">
              {fmt.price(t, decimals)}
            </text>
          </g>
        ))}
        {xTicks.map((t, i) => (
          <text key={i} x={x(t)} y={H - 8} textAnchor={i === 0 ? "start" : i === xTicks.length - 1 ? "end" : "middle"} fill="var(--fg-3)" fontSize="9" fontFamily="var(--font-mono)">
            {fmt.time(new Date(t).toISOString())}
          </text>
        ))}

        {safeBars.map((b, i) => {
          const o = Number(b.open ?? b.close), h = Number(b.high ?? b.close), l = Number(b.low ?? b.close), c = Number(b.close);
          const cx = x(times[i]);
          const up = c >= o;
          const color = up ? "var(--success)" : "var(--danger)";
          const top = y(Math.max(o, c));
          const bodyH = Math.max(1, Math.abs(y(o) - y(c)));
          return (
            <g key={i} opacity="0.86">
              <line x1={cx} x2={cx} y1={y(h)} y2={y(l)} stroke={color} strokeWidth="1" />
              <rect x={cx - candleW / 2} y={top} width={candleW} height={bodyH} rx="1" fill={up ? "transparent" : color} stroke={color} strokeWidth="1" />
            </g>
          );
        })}

        {orderedTradeLines.filter((m) => m.kind === "closed").map((m, i) => {
          const sx = x(m.visibleStartTs), sy = y(m.entry), ex = x(m.visibleEndTs), ey = y(m.exit);
          const color = m.pnl >= 0 ? "var(--success)" : "var(--danger)";
          return (
            <g
              key={`line-${m.ticket}-${i}`}
              className={`tb-trade-line ${isHovered(m.ticket) ? "tb-trade-line-active" : ""} ${isDimmed(m.ticket) ? "tb-trade-line-dim" : ""}`}
              onMouseEnter={() => setHoverTicket(m.ticket)}
              onMouseLeave={() => setHoverTicket(null)}
              onClick={() => onTicketClick?.(m.ticket)}
            >
              <line x1={sx} y1={sy} x2={ex} y2={ey} stroke={color} strokeWidth="1.6" opacity="0.88" />
              <title>{`#${m.ticket} 청산 · ${fmt.pnl(m.pnl, 2)}`}</title>
            </g>
          );
        })}

        {orderedTradeLines.filter((m) => m.kind === "open").map((m, i) => {
          const startX = x(m.visibleStartTs);
          const endX = x(xMax);
          const rows = [
            { key: "tp", value: m.tp, color: "var(--success)", label: `TP #${m.ticket}` },
            { key: "sl", value: m.sl, color: "var(--danger)", label: `SL #${m.ticket}` },
          ].filter((r) => Number.isFinite(r.value) && r.value > 0 && r.value >= yMin && r.value <= yMax);
          return rows.map((r) => (
            <g
              key={`${r.key}-${m.ticket}-${i}`}
              className={`tb-risk-line ${isHovered(m.ticket) ? "tb-risk-line-active" : ""} ${isDimmed(m.ticket) ? "tb-risk-line-dim" : ""}`}
              onMouseEnter={() => setHoverTicket(m.ticket)}
              onMouseLeave={() => setHoverTicket(null)}
              onClick={() => onTicketClick?.(m.ticket)}
            >
              <line x1={startX} x2={endX} y1={y(r.value)} y2={y(r.value)} stroke={r.color} strokeWidth="1.2" strokeDasharray="6 5" opacity="0.62" />
              <text x={endX - 4} y={y(r.value)} dy="-4" textAnchor="end" fill={r.color} fontSize="9" fontFamily="var(--font-mono)">
                {r.label} {fmt.price(r.value, decimals)}
              </text>
            </g>
          ));
        })}

        {orderedMarkers.map((m, i) => {
          const cx = x(m.ts), cy = y(m.price);
          const color = m.tone === "danger" ? "var(--danger)" : m.tone === "info" ? "var(--info)" : "var(--success)";
          return (
            <g
              key={`${m.kind}-${m.ticket}-${i}`}
              className={`tb-chart-marker ${isHovered(m.ticket) ? "tb-chart-marker-active" : ""} ${isDimmed(m.ticket) ? "tb-chart-marker-dim" : ""}`}
              onMouseEnter={() => setHoverTicket(m.ticket)}
              onMouseLeave={() => setHoverTicket(null)}
              onFocus={() => setHoverTicket(m.ticket)}
              onBlur={() => setHoverTicket(null)}
              onClick={() => onTicketClick?.(m.ticket)}
              tabIndex="0"
            >
              {m.kind === "closed-entry" ? (
                <path className="tb-marker-shape tb-marker-closed-entry-shape" d={`M ${cx} ${cy - 5} L ${cx + 5} ${cy + 5} L ${cx - 5} ${cy + 5} Z`} fill="transparent" stroke={color} strokeWidth="1.8" />
              ) : m.kind === "open-entry" ? (
                <path className="tb-marker-shape" d={`M ${cx} ${cy - 6} L ${cx + 6} ${cy + 6} L ${cx - 6} ${cy + 6} Z`} fill={color} stroke="var(--bg-0)" strokeWidth="1.2" />
              ) : (
                <circle className={`tb-marker-shape ${m.kind === "live" ? "tb-marker-live" : ""}`} cx={cx} cy={cy} r={m.kind === "live" ? 5.5 : 4.5} fill={color} stroke="var(--bg-0)" strokeWidth="1.2" />
              )}
              <circle className="tb-marker-hit" cx={cx} cy={cy} r="13" fill="transparent" />
              <title>{`${m.label} · ${fmt.price(m.price, decimals)} · ${m.detail}`}</title>
            </g>
          );
        })}
      </svg>
    </div>
  );
};

Object.assign(window, { EquityChart, DrawdownChart, BarChartH, MonthlyBars, ExposureBar, SymbolPriceChart });
