// Line chart SVG natif avec axes, ticks et plusieurs séries. Pour les usages
// compacts sans axes, préférer <Sparkline>. Ici on a besoin de lire les
// valeurs sur une grille (MRR cumulé, churn rate, etc.).

type Series = {
  name: string;
  values: number[];
  color?: string;
};

type Props = {
  /** Labels d'axe X (1 par point), ex: ['Jan', 'Fév', ...]. */
  xLabels: string[];
  series: Series[];
  width?: number;
  height?: number;
  /** Nombre approximatif de ticks Y. */
  yTicks?: number;
  /** Format des valeurs Y (et tooltips). */
  formatValue?: (v: number) => string;
  /** Force le min Y. Sinon auto. */
  minValue?: number;
  /** Force le max Y. Sinon auto. */
  maxValue?: number;
  /** Légende sous le graphe. */
  legend?: boolean;
  /** Remplit l'aire sous la 1ère série. */
  fillFirst?: boolean;
  className?: string;
};

const DEFAULT_PALETTE = [
  "var(--accent)",
  "var(--success)",
  "var(--warning)",
  "var(--danger)",
];

export function LineChart({
  xLabels,
  series,
  width = 600,
  height = 220,
  yTicks = 4,
  formatValue = (v) => String(Math.round(v)),
  minValue,
  maxValue,
  legend = true,
  fillFirst = false,
  className = "",
}: Props) {
  const padTop = 12;
  const padBottom = 24;
  const padLeft = 44;
  const padRight = 12;
  const chartW = width - padLeft - padRight;
  const chartH = height - padTop - padBottom;

  const allValues = series.flatMap((s) => s.values);
  const dataMin = allValues.length ? Math.min(...allValues) : 0;
  const dataMax = allValues.length ? Math.max(...allValues) : 1;
  // On élargit légèrement la plage pour ne pas coller la courbe aux bords.
  const min = minValue ?? Math.min(0, dataMin);
  const max = maxValue ?? (dataMax === dataMin ? dataMax + 1 : dataMax);
  const range = max - min || 1;

  const seriesWithColor = series.map((s, i) => ({
    ...s,
    color: s.color ?? DEFAULT_PALETTE[i % DEFAULT_PALETTE.length],
  }));

  const xAt = (i: number) =>
    padLeft + (xLabels.length <= 1 ? 0 : (i * chartW) / (xLabels.length - 1));
  const yAt = (v: number) => padTop + chartH - ((v - min) / range) * chartH;

  // Ticks Y "jolis" : on calcule des paliers à partir d'une magnitude.
  const ticks = computeTicks(min, max, yTicks);

  return (
    <div className={className}>
      <svg
        width="100%"
        viewBox={`0 0 ${width} ${height}`}
        preserveAspectRatio="none"
        role="img"
      >
        {/* Grille horizontale + labels Y */}
        {ticks.map((t, i) => {
          const y = yAt(t);
          return (
            <g key={i}>
              <line
                x1={padLeft}
                x2={width - padRight}
                y1={y}
                y2={y}
                stroke="var(--border, #e5e7eb)"
                strokeDasharray="2 3"
              />
              <text
                x={padLeft - 6}
                y={y + 3}
                textAnchor="end"
                className="fill-muted-foreground text-[10px] tabular-nums"
              >
                {formatValue(t)}
              </text>
            </g>
          );
        })}
        {/* Axe X labels (1 sur 2 si dense) */}
        {xLabels.map((lbl, i) => {
          const skip = xLabels.length > 8 && i % 2 !== 0 && i !== xLabels.length - 1;
          if (skip) return null;
          return (
            <text
              key={i}
              x={xAt(i)}
              y={height - 6}
              textAnchor="middle"
              className="fill-muted-foreground text-[10px]"
            >
              {lbl}
            </text>
          );
        })}
        {/* Aire optionnelle sous la 1ère série */}
        {fillFirst && seriesWithColor[0] && seriesWithColor[0].values.length > 1 && (
          <path
            d={areaPath(seriesWithColor[0].values, xAt, yAt, padTop + chartH)}
            fill={seriesWithColor[0].color}
            opacity="0.12"
          />
        )}
        {/* Lignes */}
        {seriesWithColor.map((s, i) => {
          if (s.values.length < 2) return null;
          const d = s.values
            .map((v, k) => `${k === 0 ? "M" : "L"} ${xAt(k).toFixed(1)} ${yAt(v).toFixed(1)}`)
            .join(" ");
          return (
            <g key={i}>
              <path
                d={d}
                fill="none"
                stroke={s.color}
                strokeWidth={1.8}
                strokeLinejoin="round"
                strokeLinecap="round"
              />
              {s.values.map((v, k) => (
                <circle
                  key={k}
                  cx={xAt(k)}
                  cy={yAt(v)}
                  r={2.2}
                  fill={s.color}
                >
                  <title>{`${s.name} · ${xLabels[k]} : ${formatValue(v)}`}</title>
                </circle>
              ))}
            </g>
          );
        })}
      </svg>
      {legend && series.length > 1 && (
        <ul className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-[11px] text-muted-foreground">
          {seriesWithColor.map((s, i) => (
            <li key={i} className="flex items-center gap-1.5">
              <span
                className="inline-block size-2.5 rounded-sm"
                style={{ background: s.color }}
                aria-hidden="true"
              />
              {s.name}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

function areaPath(
  values: number[],
  xAt: (i: number) => number,
  yAt: (v: number) => number,
  bottom: number,
) {
  const line = values
    .map((v, k) => `${k === 0 ? "M" : "L"} ${xAt(k).toFixed(1)} ${yAt(v).toFixed(1)}`)
    .join(" ");
  return `${line} L ${xAt(values.length - 1).toFixed(1)} ${bottom} L ${xAt(0).toFixed(1)} ${bottom} Z`;
}

// Ticks "jolis" inspirés de matplotlib. On part de la magnitude du range,
// puis on choisit un step parmi {1, 2, 5} × 10^k qui donne ~yTicks paliers.
function computeTicks(min: number, max: number, target: number): number[] {
  if (max <= min) return [min];
  const range = max - min;
  const rough = range / Math.max(1, target);
  const mag = Math.pow(10, Math.floor(Math.log10(rough)));
  const norm = rough / mag;
  let step: number;
  if (norm < 1.5) step = mag;
  else if (norm < 3.5) step = 2 * mag;
  else if (norm < 7.5) step = 5 * mag;
  else step = 10 * mag;
  const first = Math.ceil(min / step) * step;
  const ticks: number[] = [];
  for (let v = first; v <= max + 1e-9; v += step) {
    ticks.push(Number(v.toFixed(10)));
  }
  return ticks;
}
