/**
 * Shared utilities for chart primitives. Pure functions — no React.
 */

/** Linear scale: maps `[d0, d1]` → `[r0, r1]`. */
export function linearScale(
  d0: number,
  d1: number,
  r0: number,
  r1: number,
): (v: number) => number {
  const dr = d1 - d0;
  if (dr === 0) return () => (r0 + r1) / 2;
  const mr = r1 - r0;
  return (v) => r0 + ((v - d0) / dr) * mr;
}

/** Min/max of a numeric array, ignoring NaN/non-finite. */
export function extent(values: readonly number[]): [number, number] {
  let min = Number.POSITIVE_INFINITY;
  let max = Number.NEGATIVE_INFINITY;
  for (const v of values) {
    if (!Number.isFinite(v)) continue;
    if (v < min) min = v;
    if (v > max) max = v;
  }
  if (min === Number.POSITIVE_INFINITY) return [0, 1];
  if (min === max) return [min - 1, max + 1];
  return [min, max];
}

/** Format a number with `Intl.NumberFormat`, falling back to plain `.toString()`. */
export function formatNumber(
  value: number,
  opts: Intl.NumberFormatOptions = {},
): string {
  try {
    return new Intl.NumberFormat(undefined, opts).format(value);
  } catch {
    return String(value);
  }
}

/** Compact numeric formatter — `1234` → `1.2k`, `0.5` → `0.50`. */
export function compactNumber(v: number): string {
  if (!Number.isFinite(v)) return '';
  if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(1)}k`;
  return v.toFixed(Math.abs(v) < 1 ? 2 : 0);
}

/**
 * Drop non-finite (NaN / Infinity) values from a numeric series,
 * preserving the original indices. Returns the `[index, value]` pairs
 * so callers can keep their X-axis spacing intact.
 */
export function filterFinite(values: readonly number[]): Array<[number, number]> {
  const out: Array<[number, number]> = [];
  for (let i = 0; i < values.length; i++) {
    const v = values[i]!;
    if (Number.isFinite(v)) out.push([i, v]);
  }
  return out;
}

/** Build a smooth catmull-rom path through `pts`. */
export function smoothPath(pts: ReadonlyArray<[number, number]>): string {
  if (pts.length === 0) return '';
  if (pts.length === 1) {
    const [x, y] = pts[0]!;
    return `M ${x} ${y}`;
  }
  let d = `M ${pts[0]![0]} ${pts[0]![1]}`;
  for (let i = 1; i < pts.length; i++) {
    const [x0, y0] = pts[Math.max(0, i - 2)]!;
    const [x1, y1] = pts[i - 1]!;
    const [x2, y2] = pts[i]!;
    const [x3, y3] = pts[Math.min(pts.length - 1, i + 1)]!;
    const cp1x = x1 + (x2 - x0) / 6;
    const cp1y = y1 + (y2 - y0) / 6;
    const cp2x = x2 - (x3 - x1) / 6;
    const cp2y = y2 - (y3 - y1) / 6;
    d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`;
  }
  return d;
}
