// =================================================================
// ui-charts.jsx — High-grade SVG chart primitives.
//
//   - LineChart:     line + optional area fill, y-axis ticks, x-axis labels,
//                    grid, hover crosshair + tooltip
//   - BarChart:      grouped/single bars with y-axis, gridlines, tooltip
//                    (replaces the old DOM-bar version)
//   - Sparkline:     compact inline line; no axes
//   - DonutChart:    proportional donut with legend
//   - ProgressBar:   linear bar, optional value/cap labels, tones
//   - StackedBar:    horizontal segmented bar (e.g. usage by category)
//
// Charts are theme-aware: stroke/fill default to `currentColor` or
// `var(--accent)`. Pass explicit colors to override.
// =================================================================

// Pure helpers — pixel-space transforms shared by chart components.
const _niceTick = (rawStep) => {
  const exp = Math.floor(Math.log10(rawStep));
  const frac = rawStep / Math.pow(10, exp);
  const nice = frac < 1.5 ? 1 : frac < 3 ? 2 : frac < 7 ? 5 : 10;
  return nice * Math.pow(10, exp);
};
const _niceScale = (min, max, ticks = 5) => {
  if (max === min) return { min, max: max + 1, step: 1, ticks: [min, max + 1] };
  const step = _niceTick((max - min) / ticks);
  const nMin = Math.floor(min / step) * step;
  const nMax = Math.ceil(max / step) * step;
  const out = [];
  for (let v = nMin; v <= nMax + step / 2; v += step) out.push(Number(v.toFixed(8)));
  return { min: nMin, max: nMax, step, ticks: out };
};

// ---------- LineChart ----------
// data: number[] OR { x: string, y: number }[]
// series: optional — array of { data, color, label } for multi-line
const LineChart = ({
  data, series, height = 220,
  formatValue = (v) => v.toLocaleString(),
  formatLabel,
  axisLabels,
  showArea = true,
  showGrid = true,
  showYAxis = true,
  curve = "monotone",          // monotone | linear
  color,
  yTicks = 4,
}) => {
  const ref = React.useRef(null);
  const [w, setW] = React.useState(300);
  React.useEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => setW(Math.max(200, Math.floor(e.contentRect.width))));
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, []);
  const [hover, setHover] = React.useState(null);

  // Normalize to series array
  const norm = series || [{ data, color: color || "var(--accent)" }];
  const flatten = (d) => Array.isArray(d) ? d.map((v, i) => (typeof v === "object" ? v : { x: i, y: v })) : [];
  const allSeries = norm.map(s => ({ ...s, points: flatten(s.data) }));
  const n = allSeries[0]?.points.length || 0;
  if (n < 2) return <div ref={ref} style={{ height, color: "var(--muted)", fontSize: 13, padding: 12 }}>Not enough data.</div>;

  const padL = showYAxis ? 44 : 8;
  const padR = 8;
  const padT = 10;
  const padB = 24;
  const innerW = Math.max(20, w - padL - padR);
  const innerH = Math.max(20, height - padT - padB);

  const allY = allSeries.flatMap(s => s.points.map(p => p.y));
  const yScale = _niceScale(Math.min(0, ...allY), Math.max(...allY, 1), yTicks);

  const xAt = (i) => padL + (i / (n - 1)) * innerW;
  const yAt = (v) => padT + (1 - (v - yScale.min) / (yScale.max - yScale.min)) * innerH;

  // Monotone cubic path for smooth curves.
  const buildPath = (pts) => {
    if (curve === "linear" || pts.length < 3) {
      return pts.map((p, i) => `${i === 0 ? "M" : "L"} ${xAt(i)} ${yAt(p.y)}`).join(" ");
    }
    const d = pts.map((p, i) => ({ x: xAt(i), y: yAt(p.y) }));
    let path = `M ${d[0].x} ${d[0].y}`;
    for (let i = 0; i < d.length - 1; i++) {
      const p0 = d[i - 1] || d[i];
      const p1 = d[i];
      const p2 = d[i + 1];
      const p3 = d[i + 2] || p2;
      const c1x = p1.x + (p2.x - p0.x) / 6;
      const c1y = p1.y + (p2.y - p0.y) / 6;
      const c2x = p2.x - (p3.x - p1.x) / 6;
      const c2y = p2.y - (p3.y - p1.y) / 6;
      path += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2.x} ${p2.y}`;
    }
    return path;
  };

  const xLabels = axisLabels || (formatLabel
    ? allSeries[0].points.map((p, i) => formatLabel(p.x, i))
    : [`-${n - 1}`, "", "Today"]);
  // Pick a sparse set of x-axis labels at index 0, mid, end
  const xLabelIndices = axisLabels && axisLabels.length === n
    ? axisLabels.map((_, i) => i)
    : [0, Math.floor((n - 1) / 2), n - 1];

  const onMove = (e) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - rect.left - padL;
    const idx = Math.round((x / innerW) * (n - 1));
    const clamped = Math.max(0, Math.min(n - 1, idx));
    setHover(clamped);
  };

  return (
    <div ref={ref} className="hx-lc" style={{ position: "relative", width: "100%", minWidth: 0 }}>
      <svg width={w} height={height} role="img" aria-label="Line chart"
           onMouseMove={onMove} onMouseLeave={() => setHover(null)}
           style={{ display: "block", overflow: "visible", maxWidth: "100%" }}>
        <defs>
          {allSeries.map((s, si) => (
            <linearGradient key={si} id={`hxlc-grad-${si}`} x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%"   stopColor={s.color} stopOpacity="0.22"/>
              <stop offset="100%" stopColor={s.color} stopOpacity="0"/>
            </linearGradient>
          ))}
        </defs>

        {/* Gridlines + Y ticks */}
        {showGrid && yScale.ticks.map((t, i) => (
          <g key={i}>
            <line x1={padL} x2={padL + innerW} y1={yAt(t)} y2={yAt(t)}
                  stroke="var(--line-2)" strokeWidth="1" strokeDasharray={i === 0 ? "0" : "3 3"}/>
            {showYAxis && (
              <text x={padL - 8} y={yAt(t)} dy="0.32em" textAnchor="end"
                    fill="var(--muted)" fontSize="11" fontFamily="var(--mono)" style={{fontFeatureSettings:'"tnum"'}}>
                {formatValue(t)}
              </text>
            )}
          </g>
        ))}

        {/* Series — area + line */}
        {allSeries.map((s, si) => {
          const path = buildPath(s.points);
          const areaPath = `${path} L ${xAt(s.points.length - 1)} ${yAt(yScale.min)} L ${xAt(0)} ${yAt(yScale.min)} Z`;
          return (
            <g key={si}>
              {showArea && <path d={areaPath} fill={`url(#hxlc-grad-${si})`}/>}
              <path d={path} fill="none" stroke={s.color} strokeWidth="2"
                    strokeLinecap="round" strokeLinejoin="round"/>
            </g>
          );
        })}

        {/* Hover crosshair + dot */}
        {hover != null && (
          <g pointerEvents="none">
            <line x1={xAt(hover)} x2={xAt(hover)} y1={padT} y2={padT + innerH}
                  stroke="var(--ink-2)" strokeWidth="1" strokeDasharray="3 3" opacity="0.4"/>
            {allSeries.map((s, si) => (
              <circle key={si} cx={xAt(hover)} cy={yAt(s.points[hover].y)} r="4"
                      fill={s.color} stroke="var(--panel)" strokeWidth="2"/>
            ))}
          </g>
        )}

        {/* X-axis labels */}
        {xLabelIndices.map((i) => (
          <text key={i} x={xAt(i)} y={height - 6}
                textAnchor={i === 0 ? "start" : i === n - 1 ? "end" : "middle"}
                fill="var(--muted)" fontSize="11">
            {xLabels[i] || ""}
          </text>
        ))}
      </svg>

      {hover != null && (() => {
        const x = xAt(hover);
        const flip = x > w - 140;
        return (
          <div className="hx-lc-tip" style={{ left: flip ? x - 12 : x + 12, top: padT, transform: flip ? "translateX(-100%)" : "none" }}>
            <div className="hx-lc-tip-x">{xLabels[hover] != null && xLabels[hover] !== "" ? xLabels[hover] : `Point ${hover + 1}`}</div>
            {allSeries.map((s, si) => (
              <div key={si} className="hx-lc-tip-row">
                <span className="hx-lc-tip-sw" style={{ background: s.color }}/>
                {s.label && <span className="hx-lc-tip-lbl">{s.label}</span>}
                <span className="hx-lc-tip-val">{formatValue(s.points[hover].y)}</span>
              </div>
            ))}
          </div>
        );
      })()}

      <style>{`
        .hx-lc-tip {
          position: absolute; background: var(--panel); border: 1px solid var(--line);
          border-radius: 8px; box-shadow: 0 8px 24px -8px rgba(13,17,28,.18);
          padding: 8px 10px; min-width: 120px; pointer-events: none; z-index: 5;
          animation: hxfade .1s ease both;
        }
        .hx-lc-tip-x { font-size: 11.5px; color: var(--muted); margin-bottom: 4px; font-weight: 500; }
        .hx-lc-tip-row { display: flex; align-items: center; gap: 8px; font-size: 13px; font-feature-settings: "tnum"; }
        .hx-lc-tip-sw { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
        .hx-lc-tip-lbl { color: var(--muted); flex: 1; }
        .hx-lc-tip-val { font-weight: 600; font-family: var(--mono); font-size: 12.5px; margin-left: auto; }
      `}</style>
    </div>
  );
};

// ---------- BarChart (upgraded) ----------
// `data` may be number[] OR { label, value }[] OR { label, values: number[] }[]
// (the last shape renders grouped bars per `seriesColors`).
const BarChart2 = ({
  data, height = 220,
  formatValue = (v) => v.toLocaleString(),
  showGrid = true, showYAxis = true,
  color = "var(--accent)",
  seriesColors,
  axisLabels,
  yTicks = 4,
}) => {
  const ref = React.useRef(null);
  const [w, setW] = React.useState(300);
  React.useEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => setW(Math.max(200, Math.floor(e.contentRect.width))));
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, []);
  const [hover, setHover] = React.useState(null);

  // Normalize
  const rows = data.map((d, i) => {
    if (typeof d === "number") return { label: String(i), values: [d] };
    if (Array.isArray(d.values)) return d;
    return { label: d.label != null ? d.label : String(i), values: [d.value] };
  });
  const nGroups = rows.length;
  const nSeries = rows[0]?.values.length || 1;
  const allVals = rows.flatMap(r => r.values);
  const yScale = _niceScale(0, Math.max(...allVals, 1), yTicks);

  const padL = showYAxis ? 44 : 8;
  const padR = 8;
  const padT = 10;
  const padB = 24;
  const innerW = Math.max(20, w - padL - padR);
  const innerH = Math.max(20, height - padT - padB);
  const groupW = innerW / nGroups;
  const barW = Math.max(2, (groupW * 0.7) / nSeries);
  const yAt = (v) => padT + (1 - v / yScale.max) * innerH;

  // sparse x labels
  const xLabels = axisLabels || rows.map(r => r.label);
  const xLabelIndices = axisLabels && axisLabels.length === nGroups
    ? axisLabels.map((_, i) => i)
    : nGroups <= 12 ? rows.map((_, i) => i) : [0, Math.floor(nGroups / 2), nGroups - 1];

  return (
    <div ref={ref} className="hx-bc" style={{ position: "relative", width: "100%", minWidth: 0 }}>
      <svg width={w} height={height} role="img" aria-label="Bar chart"
           style={{ display: "block", overflow: "visible", maxWidth: "100%" }}
           onMouseLeave={() => setHover(null)}>
        <defs>
          {(seriesColors || [color]).slice(0, nSeries).map((c, si) => (
            <linearGradient key={si} id={`hxbc-grad-${si}`} x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor={c} stopOpacity="1"/>
              <stop offset="100%" stopColor={c} stopOpacity="0.65"/>
            </linearGradient>
          ))}
        </defs>

        {showGrid && yScale.ticks.map((t, i) => (
          <g key={i}>
            <line x1={padL} x2={padL + innerW} y1={yAt(t)} y2={yAt(t)}
                  stroke="var(--line-2)" strokeWidth="1" strokeDasharray={i === 0 ? "0" : "3 3"}/>
            {showYAxis && (
              <text x={padL - 8} y={yAt(t)} dy="0.32em" textAnchor="end"
                    fill="var(--muted)" fontSize="11" fontFamily="var(--mono)" style={{fontFeatureSettings:'"tnum"'}}>
                {formatValue(t)}
              </text>
            )}
          </g>
        ))}

        {rows.map((row, gi) => {
          const gx0 = padL + gi * groupW + (groupW - barW * nSeries) / 2;
          return (
            <g key={gi}>
              {row.values.map((v, si) => {
                const x = gx0 + si * barW;
                const y = yAt(v);
                const h = padT + innerH - y;
                const c = (seriesColors && seriesColors[si]) || color;
                const isHover = hover && hover.g === gi && hover.s === si;
                return (
                  <rect key={si} x={x} y={y} width={Math.max(1, barW - 1)} height={h}
                        fill={`url(#hxbc-grad-${si})`} rx="2"
                        opacity={hover && !isHover ? 0.6 : 1}
                        style={{ transition: "opacity .12s" }}
                        onMouseEnter={() => setHover({ g: gi, s: si, x: x + barW / 2, y, value: v, label: row.label })}/>
                );
              })}
            </g>
          );
        })}

        {xLabelIndices.map((i) => (
          <text key={i} x={padL + (i + 0.5) * groupW} y={height - 6}
                textAnchor="middle" fill="var(--muted)" fontSize="11">
            {xLabels[i]}
          </text>
        ))}
      </svg>

      {hover && (
        <div className="hx-lc-tip" style={{ left: hover.x + 12, top: hover.y - 4 }}>
          <div className="hx-lc-tip-x">{hover.label}</div>
          <div className="hx-lc-tip-row">
            <span className="hx-lc-tip-sw" style={{ background: ((seriesColors && seriesColors[hover.s]) || color) }}/>
            <span className="hx-lc-tip-val">{formatValue(hover.value)}</span>
          </div>
        </div>
      )}
    </div>
  );
};

// ---------- Sparkline ----------
const Sparkline = ({ data, width, height = 32, stroke = "var(--accent)", fill = true, strokeWidth = 1.5 }) => {
  const ref = React.useRef(null);
  const [w, setW] = React.useState(width || 80);
  React.useEffect(() => {
    if (width) return;
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => setW(Math.max(20, Math.floor(e.contentRect.width))));
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, [width]);
  if (!data || data.length < 2) return <div ref={ref} style={{ height, width: width || "100%" }}/>;
  const max = Math.max(...data);
  const min = Math.min(...data);
  const range = max - min || 1;
  const pad = 2;
  const pts = data.map((v, i) => [
    pad + (i / (data.length - 1)) * (w - pad * 2),
    pad + (1 - (v - min) / range) * (height - pad * 2),
  ]);
  const path = pts.map(([x, y], i) => `${i === 0 ? "M" : "L"} ${x} ${y}`).join(" ");
  const area = `${path} L ${pts[pts.length - 1][0]} ${height} L ${pts[0][0]} ${height} Z`;
  const gid = React.useId();
  return (
    <div ref={ref} style={{ width: width || "100%", lineHeight: 0 }}>
      <svg width={w} height={height} style={{ display: "block" }}>
        <defs>
          <linearGradient id={`hxsl-${gid}`} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor={stroke} stopOpacity="0.28"/>
            <stop offset="100%" stopColor={stroke} stopOpacity="0"/>
          </linearGradient>
        </defs>
        {fill && <path d={area} fill={`url(#hxsl-${gid})`}/>}
        <path d={path} fill="none" stroke={stroke} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"/>
      </svg>
    </div>
  );
};

// ---------- DonutChart ----------
const DonutChart = ({ data, size = 180, thickness = 22, centerLabel, centerValue, formatValue = (v) => v.toLocaleString() }) => {
  const total = data.reduce((s, d) => s + d.value, 0) || 1;
  const r = size / 2 - thickness / 2 - 1;
  const cx = size / 2, cy = size / 2;
  const circ = 2 * Math.PI * r;
  let acc = 0;
  return (
    <div className="hx-donut">
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} role="img">
        <circle cx={cx} cy={cy} r={r} stroke="var(--line-2)" strokeWidth={thickness} fill="none"/>
        {data.map((d, i) => {
          const frac = d.value / total;
          const dash = frac * circ;
          const gap = circ - dash;
          const offset = -acc * circ;
          acc += frac;
          return (
            <circle key={i} cx={cx} cy={cy} r={r}
              stroke={d.color} strokeWidth={thickness} fill="none"
              strokeDasharray={`${dash} ${gap}`}
              strokeDashoffset={offset}
              transform={`rotate(-90 ${cx} ${cy})`}
              strokeLinecap="butt"
              style={{ transition: "stroke-dasharray .3s" }}/>
          );
        })}
        {(centerValue || centerLabel) && (
          <g>
            {centerValue && (
              <text x={cx} y={cy} dy="0" textAnchor="middle"
                    fill="var(--ink)" fontSize={size * 0.16} fontWeight="600" style={{fontFeatureSettings:'"tnum"'}}>
                {centerValue}
              </text>
            )}
            {centerLabel && (
              <text x={cx} y={cy} dy={size * 0.12} textAnchor="middle"
                    fill="var(--muted)" fontSize={size * 0.07} fontWeight="500">
                {centerLabel}
              </text>
            )}
          </g>
        )}
      </svg>
      <ul className="hx-donut-legend">
        {data.map((d, i) => (
          <li key={i}>
            <span className="hx-donut-sw" style={{ background: d.color }}/>
            <span className="hx-donut-lbl">{d.label}</span>
            <span className="hx-donut-val">{formatValue(d.value)}</span>
          </li>
        ))}
      </ul>
      <style>{`
        .hx-donut { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; }
        .hx-donut-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; min-width: 200px; flex: 1; }
        .hx-donut-legend li { display: grid; grid-template-columns: 12px 1fr auto; gap: 10px; align-items: center; font-size: 13px; }
        .hx-donut-sw { width: 10px; height: 10px; border-radius: 3px; }
        .hx-donut-lbl { color: var(--ink-2); }
        .hx-donut-val { font-family: var(--mono); font-size: 12.5px; color: var(--muted); }
      `}</style>
    </div>
  );
};

// ---------- ProgressBar ----------
const ProgressBar = ({ value, max = 100, label, sub, tone = "accent", showValue = true, format }) => {
  const pct = Math.max(0, Math.min(100, (value / max) * 100));
  const fmt = format || ((v) => v.toLocaleString());
  const tones = {
    accent: "var(--accent)",
    good:   "var(--good)",
    warn:   "var(--warn)",
    bad:    "var(--bad)",
  };
  return (
    <div className="hx-pb">
      {(label || showValue) && (
        <div className="hx-pb-head">
          {label && <span className="hx-pb-lbl">{label}</span>}
          {showValue && <span className="hx-pb-val">{fmt(value)} <span style={{color:"var(--muted)"}}>/ {fmt(max)}</span></span>}
        </div>
      )}
      <div className="hx-pb-track">
        <div className="hx-pb-fill" style={{ width: pct + "%", background: tones[tone] || tone }}/>
      </div>
      {sub && <div className="hx-pb-sub">{sub}</div>}
      <style>{`
        .hx-pb { display: flex; flex-direction: column; gap: 6px; }
        .hx-pb-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
        .hx-pb-lbl { font-size: 13px; font-weight: 500; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
        .hx-pb-val { font-size: 12.5px; font-family: var(--mono); color: var(--ink-2); white-space: nowrap; flex-shrink: 0; }
        .hx-pb-track { height: 8px; background: var(--line-2); border-radius: 999px; overflow: hidden; }
        .hx-pb-fill { height: 100%; border-radius: 999px; transition: width .3s ease; }
        .hx-pb-sub { font-size: 11.5px; color: var(--muted); }
      `}</style>
    </div>
  );
};

// ---------- StackedBar (horizontal segmented) ----------
const StackedBar = ({ data, total, height = 10, showLegend = true, formatValue = (v) => v.toLocaleString() }) => {
  const t = total != null ? total : data.reduce((s, d) => s + d.value, 0) || 1;
  return (
    <div className="hx-sb">
      <div className="hx-sb-track" style={{ height }}>
        {data.map((d, i) => (
          <div key={i} className="hx-sb-seg"
               style={{ width: ((d.value / t) * 100) + "%", background: d.color }}
               title={`${d.label}: ${formatValue(d.value)}`}/>
        ))}
      </div>
      {showLegend && (
        <ul className="hx-sb-legend">
          {data.map((d, i) => (
            <li key={i}>
              <span className="hx-donut-sw" style={{ background: d.color }}/>
              <span className="hx-sb-lbl">{d.label}</span>
              <span className="hx-sb-val">{formatValue(d.value)}</span>
            </li>
          ))}
        </ul>
      )}
      <style>{`
        .hx-sb { display: flex; flex-direction: column; gap: 10px; }
        .hx-sb-track { display: flex; width: 100%; border-radius: 999px; overflow: hidden; background: var(--line-2); }
        .hx-sb-seg { transition: opacity .12s; }
        .hx-sb-seg:hover { opacity: 0.85; }
        .hx-sb-legend { list-style: none; margin: 0; padding: 0; display: flex; gap: 16px; flex-wrap: wrap; font-size: 12.5px; }
        .hx-sb-legend li { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
        .hx-sb-lbl { color: var(--ink-2); }
        .hx-sb-val { font-family: var(--mono); color: var(--muted); }
      `}</style>
    </div>
  );
};

Object.assign(window, { LineChart, BarChart2, Sparkline, DonutChart, ProgressBar, StackedBar });
