// ════════════════════════════════════════════════════════════════
// Symbiosis.DataHub — MicroViz primitives
// 8 reusable inline-SVG components. No external libs.
// All colors via var(--token), not hardcoded hex.
// ════════════════════════════════════════════════════════════════

const { useMemo } = React;

// ── hue helpers ──────────────────────────────────────────────────
function hueVar(hue) { return `var(--hue-${hue})`; }
function hueSubtle(hue) { return `var(--hue-${hue}-subtle)`; }

// ── 1. MicroSparkline ────────────────────────────────────────────
function MicroSparkline({ data, width = 64, height = 14, color = 'var(--text-secondary)', accentLast = true, fill = false }) {
  if (!data || data.length < 2) return null;
  const values = data.map(d => typeof d === 'number' ? d : d.value);
  const max = Math.max(...values);
  const min = Math.min(...values);
  const range = max - min || 1;
  const pts = values.map((v, i) => {
    const x = (i / (values.length - 1)) * (width - 2) + 1;
    const y = height - 1 - ((v - min) / range) * (height - 2);
    return [x, y];
  });
  const poly = pts.map(p => p.join(',')).join(' ');
  const lastX = pts[pts.length - 1][0];
  const lastY = pts[pts.length - 1][1];
  const areaPath = `M ${pts[0][0]},${height} L ${pts.map(p => p.join(',')).join(' L ')} L ${lastX},${height} Z`;
  return (
    <svg width={width} height={height} style={{ display: 'inline-block', verticalAlign: 'middle' }} aria-hidden="true">
      {fill && <path d={areaPath} fill={color} opacity="0.12" />}
      <polyline points={poly} fill="none" stroke={color} strokeOpacity="0.55" strokeWidth="1.2" strokeLinejoin="round" strokeLinecap="round" />
      {accentLast && <circle cx={lastX} cy={lastY} r="1.8" fill={color} />}
    </svg>
  );
}

// ── 2. MicroSimDot — 0..1 on 3-zone gradient ─────────────────────
function MicroSimDot({ value, width = 80, height = 10 }) {
  const v = Math.max(0, Math.min(1, value));
  const x = 2 + v * (width - 4);
  const zone = v < 0.5 ? 'rose' : v < 0.75 ? 'amber' : 'emerald';
  return (
    <svg width={width} height={height} style={{ display: 'inline-block', verticalAlign: 'middle' }}>
      <defs>
        <linearGradient id={`simgrad-${width}`} x1="0" x2="1">
          <stop offset="0%" stopColor="var(--hue-rose)" stopOpacity="0.35" />
          <stop offset="50%" stopColor="var(--hue-amber)" stopOpacity="0.35" />
          <stop offset="100%" stopColor="var(--hue-emerald)" stopOpacity="0.45" />
        </linearGradient>
      </defs>
      <rect x="1" y={height/2 - 1} width={width - 2} height="2" rx="1" fill={`url(#simgrad-${width})`} />
      <line x1={width * 0.5} x2={width * 0.5} y1="1" y2={height - 1} stroke="var(--border-default)" strokeWidth="0.6" strokeDasharray="1 1.5" />
      <line x1={width * 0.75} x2={width * 0.75} y1="1" y2={height - 1} stroke="var(--border-default)" strokeWidth="0.6" strokeDasharray="1 1.5" />
      <circle cx={x} cy={height / 2} r="3.2" fill={hueVar(zone)} stroke="var(--surface-elevated)" strokeWidth="1.2" />
    </svg>
  );
}

// ── 3. MicroDateDot — dot in N-day strip ─────────────────────────
function MicroDateDot({ daysAgo, span = 30, width = 60, height = 10 }) {
  const d = Math.max(0, Math.min(span, daysAgo));
  const x = (1 - d / span) * (width - 4) + 2;
  return (
    <svg width={width} height={height} style={{ display: 'inline-block', verticalAlign: 'middle' }}>
      <line x1="2" x2={width - 2} y1={height / 2} y2={height / 2} stroke="var(--border-default)" strokeWidth="1" />
      {[0, 7, 14, 21].map(t => (
        <line key={t} x1={2 + (t / span) * (width - 4)} x2={2 + (t / span) * (width - 4)}
              y1={height / 2 - 1.5} y2={height / 2 + 1.5} stroke="var(--text-tertiary)" strokeOpacity="0.5" strokeWidth="0.6" />
      ))}
      <circle cx={x} cy={height / 2} r="2.4" fill="var(--brand-bronze)" />
    </svg>
  );
}

// ── 4. CategoryPill — coloured pill ──────────────────────────────
function CategoryPill({ hue = 'info', children, small = false }) {
  return (
    <span className="sdh-hpill" style={{
      background: hueSubtle(hue),
      color: hueVar(hue),
      borderColor: hueVar(hue),
      borderStyle: 'solid',
      opacity: 1,
      ...(small ? { padding: '2px 7px', fontSize: '10.5px' } : null),
    }}>
      <span style={{
        width: 5, height: 5, borderRadius: '50%', background: hueVar(hue), display: 'inline-block', flexShrink: 0
      }} />
      {children}
    </span>
  );
}

// ── 5. MomentumPill — rising / steady / cooling ──────────────────
function MomentumPill({ momentum, growth }) {
  const config = {
    rising:  { hue: 'emerald', icon: '↑', label: 'растёт' },
    steady:  { hue: 'info',    icon: '→', label: 'стабильно' },
    cooling: { hue: 'rose',    icon: '↓', label: 'снижается' },
  };
  const c = config[momentum] || config.steady;
  const pct = growth != null ? `${growth > 0 ? '+' : ''}${Math.round(growth * 100)}%` : null;
  return (
    <span className="sdh-hpill" style={{
      background: hueSubtle(c.hue),
      color: hueVar(c.hue),
      borderColor: hueVar(c.hue),
      borderStyle: 'solid',
    }}>
      <span style={{ fontWeight: 700, fontSize: 13, lineHeight: 1 }}>{c.icon}</span>
      <span>{c.label}</span>
      {pct && <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10.5, opacity: 0.85 }}>{pct}</span>}
    </span>
  );
}

// ── 6. NicheScatter — interactive, with greedy label placement ───
function NicheScatter({ clusters, width = 880, height = 340, onSelect, activeId }) {
  const [hover, setHover] = React.useState(null);
  const pad = { l: 64, r: 40, t: 28, b: 56 };
  const W = width - pad.l - pad.r;
  const H = height - pad.t - pad.b;

  const dots = useMemo(() => clusters.map(c => {
    const x = Math.max(0.02, Math.min(0.98, (c.growth + 0.4) / 0.8));
    const y = Math.max(0.05, Math.min(0.95, c.count / 80));
    const hue = c.momentum === 'rising' ? 'emerald' : c.momentum === 'cooling' ? 'rose' : 'info';
    const r = 6 + Math.min(14, c.count / 8);
    return {
      ...c, x, y, hue, r,
      cx: pad.l + x * W,
      cy: pad.t + (1 - y) * H,
    };
  }), [clusters, width, height]);

  const labels = useMemo(() => placeLabels(dots, pad.l + W, pad.l), [dots, pad.l, W]);
  const focused = hover || activeId;

  return (
    <svg width="100%" viewBox={`0 0 ${width} ${height}`} style={{ display: 'block', maxWidth: '100%' }}>
      {/* axes */}
      <line x1={pad.l} x2={pad.l + W} y1={pad.t + H} y2={pad.t + H} stroke="var(--border-default)" />
      <line x1={pad.l} x2={pad.l} y1={pad.t} y2={pad.t + H} stroke="var(--border-default)" />

      {/* center vertical (zero growth) */}
      <line x1={pad.l + W * 0.5} x2={pad.l + W * 0.5} y1={pad.t} y2={pad.t + H}
            stroke="var(--border-default)" strokeDasharray="2 4" />

      {/* zone labels */}
      <text x={pad.l + 6} y={pad.t + 14} fontSize="10" fontFamily="var(--font-mono)" fill="var(--text-tertiary)">снижение</text>
      <text x={pad.l + W - 6} y={pad.t + 14} fontSize="10" fontFamily="var(--font-mono)" fill="var(--text-tertiary)" textAnchor="end">рост</text>

      {/* axis titles */}
      <text x={pad.l - 8} y={pad.t + 8} fontSize="10" fontFamily="var(--font-mono)" fill="var(--text-tertiary)" textAnchor="end">объём</text>
      <text x={pad.l - 8} y={pad.t + H} fontSize="10" fontFamily="var(--font-mono)" fill="var(--text-tertiary)" textAnchor="end">мало</text>
      <text x={pad.l + W / 2} y={pad.t + H + 36} fontSize="10" fontFamily="var(--font-mono)" fill="var(--text-tertiary)" textAnchor="middle">импульс кластера (∆ к предыдущей неделе)</text>

      {/* dots */}
      {dots.map(d => {
        const isF = focused === d.id;
        const isDim = focused && !isF;
        return (
          <g key={d.id}
             onMouseEnter={() => setHover(d.id)}
             onMouseLeave={() => setHover(null)}
             onClick={() => onSelect && onSelect(d.id)}
             style={{ cursor: 'pointer' }}>
            <circle cx={d.cx} cy={d.cy} r={d.r}
              fill={`var(--hue-${d.hue})`}
              fillOpacity={isF ? 0.32 : isDim ? 0.06 : 0.18}
              stroke={`var(--hue-${d.hue})`}
              strokeWidth={isF ? 2 : 1.2}
              strokeOpacity={isDim ? 0.35 : 1}
              style={{ transition: 'fill-opacity .15s, stroke-width .15s, stroke-opacity .15s' }} />
            <circle cx={d.cx} cy={d.cy} r="2"
              fill={`var(--hue-${d.hue})`}
              fillOpacity={isDim ? 0.35 : 1}
              style={{ transition: 'fill-opacity .15s' }} />
          </g>
        );
      })}

      {/* labels (greedy-placed, with leader lines if offset) */}
      {labels.map(l => {
        if (!l.pos) return null;
        const isF = focused === l.dot.id;
        const isDim = focused && !isF;
        return (
          <g key={l.dot.id} style={{ pointerEvents: 'none' }}>
            {l.leader && (
              <line x1={l.leader.x1} y1={l.leader.y1} x2={l.leader.x2} y2={l.leader.y2}
                stroke="var(--text-tertiary)" strokeOpacity={isDim ? 0.18 : 0.4} strokeWidth="0.6" />
            )}
            {/* readability backdrop */}
            <rect x={l.rect.x} y={l.rect.y} width={l.rect.w} height={l.rect.h}
              fill="var(--surface-elevated)" fillOpacity={isF ? 0.95 : (isDim ? 0.6 : 0.78)}
              rx="3" />
            <text x={l.labelX} y={l.labelY}
              fontSize="10.5" fontFamily="var(--font-sans)"
              fontWeight={isF ? 600 : 400}
              fill={isDim ? 'var(--text-tertiary)' : isF ? 'var(--text-primary)' : 'var(--text-secondary)'}
              textAnchor={l.pos.anchor}
              style={{ transition: 'fill .15s' }}>
              {l.dot.title}
            </text>
          </g>
        );
      })}

      {/* Tooltip for focused dot whose label couldn't be placed, or always for hovered */}
      {hover && (() => {
        const d = dots.find(x => x.id === hover);
        if (!d) return null;
        const tipW = 200, tipH = 56;
        let tx = d.cx + d.r + 10;
        let ty = d.cy - tipH / 2;
        if (tx + tipW > width - 4) tx = d.cx - d.r - 10 - tipW;
        if (ty < pad.t + 4) ty = pad.t + 4;
        if (ty + tipH > pad.t + H - 4) ty = pad.t + H - 4 - tipH;
        const arrow = d.momentum === 'rising' ? '↑' : d.momentum === 'cooling' ? '↓' : '→';
        const pct = `${d.growth > 0 ? '+' : ''}${Math.round(d.growth * 100)}%`;
        return (
          <g style={{ pointerEvents: 'none' }}>
            <rect x={tx} y={ty} width={tipW} height={tipH} rx="6"
              fill="var(--surface-elevated)" stroke={`var(--hue-${d.hue})`} strokeWidth="1" />
            <text x={tx + 10} y={ty + 18} fontSize="12" fontFamily="var(--font-sans)" fontWeight="600" fill="var(--text-primary)">
              {d.title.length > 28 ? d.title.slice(0, 26) + '…' : d.title}
            </text>
            <text x={tx + 10} y={ty + 36} fontSize="11" fontFamily="var(--font-mono)" fill="var(--text-secondary)">
              {d.count} стартапов
            </text>
            <text x={tx + 10} y={ty + 50} fontSize="11" fontFamily="var(--font-mono)" fill={`var(--hue-${d.hue})`}>
              {arrow} {pct} к прошлой неделе
            </text>
          </g>
        );
      })()}
    </svg>
  );
}

// Greedy label placement: tries 4 anchor positions per dot, skips on collision
function placeLabels(dots, maxX, minX) {
  // priority: large dots first so they get prime spots
  const sorted = [...dots].sort((a, b) => b.r - a.r);
  const placed = [];
  const charW = 6.0;
  const charH = 13;
  // candidates: right, left, top, bottom, slight diagonals
  const candidates = (d) => [
    { dx: d.r + 6,  dy: 4,           anchor: 'start',  leader: false },
    { dx: -d.r - 6, dy: 4,           anchor: 'end',    leader: false },
    { dx: 0,        dy: -d.r - 6,    anchor: 'middle', leader: false },
    { dx: 0,        dy: d.r + 14,    anchor: 'middle', leader: false },
    { dx: d.r + 18, dy: -d.r - 4,    anchor: 'start',  leader: true  },
    { dx: -d.r - 18, dy: -d.r - 4,   anchor: 'end',    leader: true  },
    { dx: d.r + 18, dy: d.r + 14,    anchor: 'start',  leader: true  },
    { dx: -d.r - 18, dy: d.r + 14,   anchor: 'end',    leader: true  },
  ];

  sorted.forEach(dot => {
    const w = dot.title.length * charW + 8;
    let chosen = null;
    for (const c of candidates(dot)) {
      const labelX = dot.cx + c.dx;
      const labelY = dot.cy + c.dy;
      const rectX = c.anchor === 'end' ? labelX - w + 2
                  : c.anchor === 'middle' ? labelX - w / 2
                  : labelX - 2;
      const rect = { x: rectX, y: labelY - charH + 3, w, h: charH };
      // bounds check
      if (rect.x < minX - 8) continue;
      if (rect.x + rect.w > maxX + 8) continue;
      // collision with placed label rects
      let bad = placed.some(p => p.rect && rectsOverlap(rect, p.rect, 2));
      // collision with any dot (including unplaced ones)
      if (!bad) bad = dots.some(other => other.id !== dot.id && circleRectOverlap(other, rect, 1));
      if (bad) continue;
      chosen = { pos: c, rect, labelX, labelY, leader: null };
      if (c.leader) {
        chosen.leader = {
          x1: dot.cx + Math.cos(Math.atan2(c.dy, c.dx)) * dot.r,
          y1: dot.cy + Math.sin(Math.atan2(c.dy, c.dx)) * dot.r,
          x2: c.anchor === 'start' ? rect.x : c.anchor === 'end' ? rect.x + rect.w : labelX,
          y2: labelY - 3,
        };
      }
      break;
    }
    placed.push({ dot, ...(chosen || { pos: null }) });
  });
  return placed;
}

function rectsOverlap(a, b, pad = 0) {
  return !(a.x + a.w + pad < b.x || b.x + b.w + pad < a.x || a.y + a.h + pad < b.y || b.y + b.h + pad < a.y);
}
function circleRectOverlap(c, r, pad = 0) {
  const cx = Math.max(r.x, Math.min(c.cx, r.x + r.w));
  const cy = Math.max(r.y, Math.min(c.cy, r.y + r.h));
  const dx = c.cx - cx, dy = c.cy - cy;
  return (dx * dx + dy * dy) < ((c.r + pad) * (c.r + pad));
}

// ── 7. CompetitorBeeswarm — 32px tall ────────────────────────────
function CompetitorBeeswarm({ verdicts, width = 220, height = 32 }) {
  // verdicts: { risky, validate, viable } — counts
  if (!verdicts) return null;
  const dotR = 4;
  const groups = [
    { key: 'risky',    count: verdicts.risky,    hue: 'rose'    },
    { key: 'validate', count: verdicts.validate, hue: 'amber'   },
    { key: 'viable',   count: verdicts.viable,   hue: 'emerald' },
  ];
  const total = groups.reduce((s, g) => s + g.count, 0) || 1;
  const segW = width / 3;

  const dots = [];
  groups.forEach((g, gi) => {
    for (let i = 0; i < g.count; i++) {
      const cols = Math.max(1, Math.floor(segW / (dotR * 2 + 2)));
      const col = i % cols;
      const row = Math.floor(i / cols);
      const cx = gi * segW + (segW / 2) - (cols * (dotR * 2 + 2)) / 2 + col * (dotR * 2 + 2) + dotR + 1;
      const cy = height / 2 - row * (dotR * 2 + 1) + dotR;
      dots.push({ cx, cy, hue: g.hue, key: `${g.key}-${i}` });
    }
  });

  return (
    <svg width="100%" viewBox={`0 0 ${width} ${height}`} style={{ display: 'block' }}>
      {[1, 2].map(i => (
        <line key={i} x1={i * segW} x2={i * segW} y1="2" y2={height - 2}
              stroke="var(--border-subtle)" strokeDasharray="1 2" />
      ))}
      {dots.map(d => (
        <circle key={d.key} cx={d.cx} cy={d.cy} r={dotR} fill={hueVar(d.hue)} fillOpacity="0.8" />
      ))}
    </svg>
  );
}

// ── 8. IdeaHorizon — small horizon chart ─────────────────────────
function IdeaHorizon({ series, width = 120, height = 22, hue = 'info' }) {
  if (!series || series.length < 2) return null;
  const values = series.map(d => typeof d === 'number' ? d : d.value);
  const max = Math.max(...values, 1);
  const bw = width / values.length;
  return (
    <svg width={width} height={height} style={{ display: 'inline-block', verticalAlign: 'middle' }}>
      {values.map((v, i) => {
        const h = (v / max) * height;
        return <rect key={i} x={i * bw} y={height - h} width={Math.max(1, bw - 0.7)} height={h}
                     fill={hueVar(hue)} opacity={0.3 + 0.55 * (v / max)} />;
      })}
    </svg>
  );
}

// ── exports to window ────────────────────────────────────────────
Object.assign(window, {
  MicroSparkline,
  MicroSimDot,
  MicroDateDot,
  CategoryPill,
  MomentumPill,
  NicheScatter,
  CompetitorBeeswarm,
  IdeaHorizon,
});
