/* Starboard · Interactive chart components
   Thin React wrappers around the dependency-free adChart renderer
   (chart-core.js). Load order:
     <script src="../ui_kits/charts/chart-core.js"></script>
     <script type="text/babel" src="../ui_kits/charts/Charts.jsx"></script>
   Styles: charts-interactive.css (imports charts.css). */

/* ── shared hook: mount / update / destroy an adChart ───────── */
const useAdChart = (config) => {
  const hostRef = React.useRef(null);
  const chartRef = React.useRef(null);
  const json = JSON.stringify(config, (k, v) => (typeof v === 'function' ? String(v) : v));
  React.useEffect(() => {
    if (!hostRef.current) return;
    if (!chartRef.current) chartRef.current = adChart(hostRef.current, config);
    else chartRef.current.update(config);
  }, [json]);
  React.useEffect(() => () => {
    if (chartRef.current) { chartRef.current.destroy(); chartRef.current = null; }
  }, []);
  return hostRef;
};

const makeChart = (type, extra) => (props) => {
  const { className, style, ...rest } = props;
  const hostRef = useAdChart(Object.assign({ type }, extra, rest));
  return <div ref={hostRef} className={className} style={style} />;
};

/* ── per-family components ──────────────────────────────────── */
const LineChart    = makeChart('line');
const AreaChart    = makeChart('area');
const BarChart     = makeChart('bar');
const ComboChart   = makeChart('combo');
const ScatterChart = makeChart('scatter');
const Sparkline    = makeChart('sparkline');
const DonutChart   = makeChart('donut');
const GaugeChart   = makeChart('gauge');
const HeatmapChart = makeChart('heatmap');
const FunnelChart  = makeChart('funnel');
const TreemapChart = makeChart('treemap');

/* ── ChartFrame — the mandatory wrapper for every chart ───────
      section.ad-chart-frame
        header.ad-chart-header
          div > h3.ad-chart-title + p.ad-chart-description
          div.ad-chart-meta            (meta text / range chips / actions)
        div.ad-chart-body > children
      Same card rhythm as every Starboard card: white bg, 1px
      neutral border, --radius-lg, shadow-xs. `sub` is accepted
      as a legacy alias of `description`; ranges/actions render
      inside the meta slot so ChartCard stays a thin alias. ──── */
const ChartFrame = ({ title, description, sub, meta, actions, ranges, range, onRangeChange, children, footer, className }) => {
  const desc = description != null ? description : sub;
  const chips = ranges && ranges.map((r) => (
    <button
      key={r}
      type="button"
      className={`ch-chip${r === range ? ' is-active' : ''}`}
      aria-pressed={r === range}
      onClick={() => onRangeChange && onRangeChange(r)}
    >{r}</button>
  ));
  const hasMeta = meta != null || chips != null || actions != null;
  return (
    <section className={`ad-chart-frame${className ? ' ' + className : ''}`}>
      <header className="ad-chart-header">
        <div>
          <h3 className="ad-chart-title">{title}</h3>
          {desc != null && <p className="ad-chart-description">{desc}</p>}
        </div>
        {hasMeta && <div className="ad-chart-meta">{chips}{meta}{actions}</div>}
      </header>
      <div className="ad-chart-body">{children}</div>
      {footer}
    </section>
  );
};

/* ChartCard — thin alias of ChartFrame for backwards compat */
const ChartCard = ChartFrame;

/* ── ChartSkeleton — the ONE shared loading structure ─────────
      Same ad-shimmer bones / spacing for every chart family
      (chart-core renders the identical markup for state:'loading') */
const ChartSkeleton = ({ height = 160 }) => (
  <div className="chx-skeleton" style={{ height }} aria-hidden="true">
    {[0.45, 0.7, 0.35, 0.85, 0.55, 0.65, 0.4, 0.75].map((h, i) => (
      <div key={i} className="chx-bone" style={{ height: Math.round(h * (height - 16)) }} />
    ))}
  </div>
);

/* ── KpiTile — fixed 3-row structure ──────────────────────────
      (1) label left + optional status icon right
      (2) big KPI value + delta
      (3) sparkline full width, fixed 36px, inside a padded frame.
      The value cross-fades over --motion-status-change on change
      ONLY; hover lifts the container, never text or sparkline. */
const KpiTile = ({ label, value, delta, deltaDir, icon, data, seriesKey = 'v', color = 's1', format }) => {
  const [shown, setShown] = React.useState(value);
  const [swapping, setSwapping] = React.useState(false);
  React.useEffect(() => {
    if (value === shown) return;
    const rm = window.adPrefersReducedMotion
      ? adPrefersReducedMotion()
      : window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (rm) { setShown(value); return; }
    setSwapping(true);
    const t = setTimeout(() => { setShown(value); setSwapping(false); }, 180);
    return () => clearTimeout(t);
  }, [value]);

  return (
    <div className="ad-kpi-tile">
      <div className="ad-kpi-tile__top">
        <span className="ad-kpi-tile__label">{label}</span>
        {icon && <span className="ad-kpi-tile__icon">{icon}</span>}
      </div>
      <div className="ad-kpi-tile__value-row">
        <span className={`ch-value chx-kpi-value${swapping ? ' is-swapping' : ''}`}>
          {format ? format(shown) : shown}
        </span>
        {delta != null && (
          <span className={`ch-delta ch-delta--${deltaDir === 'down' ? 'down' : 'up'}`}>
            {deltaDir === 'down' ? '▾' : '▴'} {delta}
          </span>
        )}
      </div>
      {data && data.length > 0 && (
        <div className="ad-kpi-tile__spark">
          <Sparkline data={data} series={[{ key: seriesKey, label, color }]} height={36} />
        </div>
      )}
    </div>
  );
};

Object.assign(window, {
  ChartFrame, ChartCard, ChartSkeleton, KpiTile,
  LineChart, AreaChart, BarChart, ComboChart, ScatterChart, Sparkline,
  DonutChart, GaugeChart, HeatmapChart, FunnelChart, TreemapChart,
  useAdChart,
});
