// Main React component for the stock DD workspace
const { useState, useEffect, useMemo, useRef, useCallback } = React;

// ——————————————— helpers
const fmtPct = (n) => {
  if (n == null || isNaN(n)) return "—";
  return (n >= 0 ? "+" : "") + n.toFixed(2) + "%";
};
const fmtPrice = (n) => {
  if (n == null || isNaN(n)) return "—";
  return "¥" + n.toLocaleString(undefined, { maximumFractionDigits: 0 });
};
const sectorOrder = [
  "Materials",
  "Equipment",
  "Manufacturer",
  "Integrator",
  "End User",
];
const sectorIdx = (s) => Math.max(0, sectorOrder.indexOf(s));
window.fmtPct = fmtPct;
window.fmtPrice = fmtPrice;
window.sectorOrder = sectorOrder;

// Smart abbreviator: fit a company name into `maxChars` chars for in-circle labels.
// Strategy: drop filler ("Co","Ltd","Industries","Holdings","Electric","Heavy","Chemical","Corp"),
// take first word or use acronym if multi-word, else truncate.
const _NAME_FILLER =
  /\b(Co|Co\.|Ltd|Inc|Corp|Corporation|Holdings?|Industries?|Industry|Electric|Electronics|Heavy|Chemical|Group|The|&|and)\b\.?/gi;
const _NAME_CACHE = {};
function shortName(name, maxChars) {
  if (!name) return "";
  const key = name + "|" + maxChars;
  if (_NAME_CACHE[key]) return _NAME_CACHE[key];
  let s = name
    .replace(_NAME_FILLER, " ")
    .replace(/[\s\-·]+/g, " ")
    .trim();
  if (!s) s = name.trim();
  if (s.length <= maxChars) return (_NAME_CACHE[key] = s);
  // Try first word
  const first = s.split(/\s+/)[0];
  if (first.length <= maxChars) return (_NAME_CACHE[key] = first);
  // Try acronym if multi-word
  const parts = s.split(/\s+/);
  if (parts.length >= 2) {
    const ac = parts
      .map((p) => p[0])
      .join("")
      .toUpperCase();
    if (ac.length <= maxChars && ac.length >= 2) return (_NAME_CACHE[key] = ac);
  }
  // Truncate
  return (_NAME_CACHE[key] = first.slice(0, maxChars));
}
window.shortName = shortName;

function themesForTicker(t) {
  return window.THEMES.filter((th) => (th.tickers || []).includes(t));
}
function peersOf(t) {
  const set = new Set();
  window.LINKS.forEach((l) => {
    if (l.from === t) set.add(l.to);
    if (l.to === t) set.add(l.from);
  });
  return [...set];
}
function linksOf(t) {
  return window.LINKS.filter((l) => l.from === t || l.to === t);
}
function newsForTicker(t) {
  return window.NEWS.filter((n) => (n.tickers || []).includes(t));
}

// ——————————————— Sparkline
function Sparkline({ data, width = 120, height = 32, color, glow = true }) {
  if (!data || !data.length) return null;
  const min = Math.min(...data),
    max = Math.max(...data);
  const range = max - min || 1;
  const pts = data.map((v, i) => [
    (i / Math.max(1, data.length - 1)) * width,
    height - ((v - min) / range) * height * 0.9 - height * 0.05,
  ]);
  const d = pts
    .map(
      (p, i) => (i === 0 ? "M" : "L") + p[0].toFixed(1) + " " + p[1].toFixed(1),
    )
    .join(" ");
  const last = pts[pts.length - 1];
  const up = data[data.length - 1] >= data[0];
  const c = color || (up ? "oklch(78% 0.17 155)" : "oklch(68% 0.19 25)");
  return (
    <svg width={width} height={height} className="spark">
      <defs>
        <linearGradient
          id={`sg-${width}-${color || "d"}`}
          x1="0"
          x2="0"
          y1="0"
          y2="1"
        >
          <stop offset="0" stopColor={c} stopOpacity="0.25" />
          <stop offset="1" stopColor={c} stopOpacity="0" />
        </linearGradient>
      </defs>
      <path
        d={d + ` L${width} ${height} L0 ${height} Z`}
        fill={`url(#sg-${width}-${color || "d"})`}
      />
      <path
        d={d}
        fill="none"
        stroke={c}
        strokeWidth="1.5"
        strokeLinecap="round"
      />
      {glow && (
        <circle cx={last[0]} cy={last[1]} r="2.5" fill={c}>
          <animate
            attributeName="r"
            values="2.5;4;2.5"
            dur="2s"
            repeatCount="indefinite"
          />
        </circle>
      )}
    </svg>
  );
}

// ——————————————— Price chart (main)
// data is [{d: "2025-10-17", c: 16870}, ...] or [{d: "2026-04-16 09:00", c: 28625}, ...]
function PriceChart({
  ticker,
  data,
  news,
  events = [],
  onHover,
  timeframe = "6M",
}) {
  const W = 780,
    H = 280,
    pad = { l: 44, r: 16, t: 20, b: 28 };
  const svgRef = useRef(null);
  const [hover, setHover] = useState(null);
  const [drawn, setDrawn] = useState(0);

  useEffect(() => {
    setDrawn(0);
    let raf;
    const start = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - start) / 900);
      setDrawn(1 - Math.pow(1 - t, 3));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [ticker]);

  if (!data || data.length < 2) return null;
  // Extract close values and dates from {d, c} objects
  const closes = data.map((p) => (typeof p === "number" ? p : p.c || 0));
  const dates = data.map((p) => (typeof p === "number" ? "" : p.d || ""));
  const min = Math.min(...closes),
    max = Math.max(...closes);
  const range = max - min || 1;
  const x = (i) =>
    pad.l + (i / Math.max(1, closes.length - 1)) * (W - pad.l - pad.r);
  const y = (v) => pad.t + (1 - (v - min) / range) * (H - pad.t - pad.b);
  const pts = closes.map((v, i) => [x(i), y(v)]);
  const d = pts
    .map(
      (p, i) => (i === 0 ? "M" : "L") + p[0].toFixed(1) + " " + p[1].toFixed(1),
    )
    .join(" ");
  const up = closes[closes.length - 1] >= closes[0];
  const c = up ? "oklch(78% 0.17 155)" : "oklch(68% 0.19 25)";

  // Map recent news to x positions
  const eventMarkers = events.slice(0, 6).map((n, i) => {
    const idx = Math.max(0, closes.length - 1 - i * 3);
    return { ...n, idx, x: x(idx), y: y(closes[idx]) };
  });

  function onMove(e) {
    const svg = svgRef.current;
    const rect = svg.getBoundingClientRect();
    const mx = (e.clientX - rect.left) * (W / rect.width);
    const i = Math.round(
      ((mx - pad.l) / (W - pad.l - pad.r)) * (closes.length - 1),
    );
    if (i >= 0 && i < closes.length)
      setHover({ i, v: closes[i], date: dates[i], x: x(i), y: y(closes[i]) });
  }

  // Visible length via drawn
  const visibleLen = Math.max(2, Math.round(data.length * drawn));
  const visiblePts = pts.slice(0, visibleLen);
  const visibleD = visiblePts
    .map(
      (p, i) => (i === 0 ? "M" : "L") + p[0].toFixed(1) + " " + p[1].toFixed(1),
    )
    .join(" ");

  const gridY = [0, 0.25, 0.5, 0.75, 1].map((t) => min + t * range);

  return (
    <svg
      ref={svgRef}
      viewBox={`0 0 ${W} ${H}`}
      className="price-chart"
      onMouseMove={onMove}
      onMouseLeave={() => setHover(null)}
    >
      <defs>
        <linearGradient id="pc-fill" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0" stopColor={c} stopOpacity="0.22" />
          <stop offset="1" stopColor={c} stopOpacity="0" />
        </linearGradient>
        <filter id="pc-glow" x="-10%" y="-10%" width="120%" height="120%">
          <feGaussianBlur stdDeviation="2.5" result="b" />
          <feMerge>
            <feMergeNode in="b" />
            <feMergeNode in="SourceGraphic" />
          </feMerge>
        </filter>
      </defs>
      {/* grid */}
      {gridY.map((v, i) => (
        <g key={i}>
          <line
            x1={pad.l}
            x2={W - pad.r}
            y1={y(v)}
            y2={y(v)}
            stroke="var(--line-faint)"
            strokeDasharray="2 4"
          />
          <text x={pad.l - 8} y={y(v) + 3} textAnchor="end" className="axis">
            {Math.round(v).toLocaleString()}
          </text>
        </g>
      ))}
      {/* x-axis ticks — from real dates */}
      {(() => {
        const n = dates.length;
        const count = Math.min(6, n);
        if (count < 2) return null;
        const labels = [];
        for (let i = 0; i < count; i++) {
          const idx = Math.round((i / (count - 1)) * (n - 1));
          const raw = dates[idx] || "";
          // Format: "2025-10-17" → "Oct 17" or "2026-04-16 09:00" → "09:00"
          let lbl = raw;
          if (raw.includes(" ")) {
            lbl = raw.split(" ")[1];
          } else if (raw.length >= 10) {
            const m = [
              "Jan",
              "Feb",
              "Mar",
              "Apr",
              "May",
              "Jun",
              "Jul",
              "Aug",
              "Sep",
              "Oct",
              "Nov",
              "Dec",
            ];
            const parts = raw.split("-");
            lbl = m[parseInt(parts[1], 10) - 1] + " " + parseInt(parts[2], 10);
          }
          labels.push({ idx, lbl });
        }
        return labels.map((l) => (
          <text
            key={l.idx}
            x={x(l.idx)}
            y={H - pad.b + 16}
            textAnchor="middle"
            className="axis"
          >
            {l.lbl}
          </text>
        ));
      })()}
      {/* area */}
      {visiblePts.length > 1 && (
        <path
          d={
            visibleD +
            ` L${visiblePts[visiblePts.length - 1][0]} ${H - pad.b} L${visiblePts[0][0]} ${H - pad.b} Z`
          }
          fill="url(#pc-fill)"
        />
      )}
      {/* line */}
      <path
        d={visibleD}
        fill="none"
        stroke={c}
        strokeWidth="1.75"
        filter="url(#pc-glow)"
        strokeLinecap="round"
      />
      {/* event markers */}
      {drawn > 0.95 &&
        eventMarkers.map((ev, i) => (
          <g
            key={ev.id}
            className="evt-marker"
            onMouseEnter={() => onHover && onHover(ev)}
            onMouseLeave={() => onHover && onHover(null)}
          >
            <line
              x1={ev.x}
              x2={ev.x}
              y1={pad.t}
              y2={H - pad.b}
              stroke="var(--line-faint)"
              strokeDasharray="1 3"
            />
            <circle
              cx={ev.x}
              cy={ev.y}
              r="4.5"
              fill="var(--bg-1)"
              stroke={
                ev.sentiment > 0 ? "oklch(78% 0.17 155)" : "oklch(68% 0.19 25)"
              }
              strokeWidth="1.5"
            />
            <circle
              cx={ev.x}
              cy={ev.y}
              r="2"
              fill={
                ev.sentiment > 0 ? "oklch(78% 0.17 155)" : "oklch(68% 0.19 25)"
              }
            >
              <animate
                attributeName="r"
                values="2;3.5;2"
                dur="2.4s"
                repeatCount="indefinite"
                begin={`${i * 0.3}s`}
              />
            </circle>
          </g>
        ))}
      {/* hover */}
      {hover && (
        <g>
          <line
            x1={hover.x}
            x2={hover.x}
            y1={pad.t}
            y2={H - pad.b}
            stroke="var(--fg-3)"
            strokeDasharray="2 3"
          />
          <circle
            cx={hover.x}
            cy={hover.y}
            r="4"
            fill={c}
            stroke="var(--bg-0)"
            strokeWidth="2"
          />
          <g
            transform={`translate(${Math.min(hover.x + 10, W - 110)}, ${Math.max(hover.y - 34, pad.t + 4)})`}
          >
            <rect
              width="100"
              height="32"
              rx="6"
              fill="var(--bg-2)"
              stroke="var(--line)"
            />
            <text x="10" y="14" className="tt-lbl">
              {hover.date || "Day " + (hover.i + 1)}
            </text>
            <text x="10" y="26" className="tt-val">
              {fmtPrice(hover.v)}
            </text>
          </g>
        </g>
      )}
    </svg>
  );
}

window.Sparkline = Sparkline;
window.PriceChart = PriceChart;
window.fmtPct = fmtPct;
window.fmtPrice = fmtPrice;
window.sectorOrder = sectorOrder;
window.sectorIdx = sectorIdx;
window.themesForTicker = themesForTicker;
window.peersOf = peersOf;
window.linksOf = linksOf;
window.newsForTicker = newsForTicker;
