/**
 * Metric value formatting + axis scaling for chart widgets.
 *
 * The data sources (Prometheus range queries, qBittorrent speed counters)
 * return raw numbers. Charts display them with a consistent decimal-prefix
 * scale (kB/MB/GB, kbps/Mbps/Gbps, …) chosen from the series' magnitude.
 *
 * Convention: decimal (base 1000) prefixes k/M/G/T — matches networking
 * (Mbps, MB/s) and the kB/MB/GB phrasing users expect for storage/speed.
 */

export type MetricUnit =
	| "none"
	| "bytes"
	| "bytes_per_sec"
	| "bits_per_sec"
	| "bits"
	| "percent"
	| "seconds";

export type MetricScale = "auto" | "k" | "m" | "g" | "t";

const BASE = 1000;
const PREFIXES = ["", "k", "M", "G", "T"] as const;
const FIXED_INDEX: Record<Exclude<MetricScale, "auto">, number> = {
	k: 1,
	m: 2,
	g: 3,
	t: 4,
};

/** Base unit suffix appended after the prefix (e.g. "B", "B/s", "bps"). */
const UNIT_SUFFIX: Record<
	Exclude<MetricUnit, "percent" | "seconds">,
	string
> = {
	none: "",
	bytes: "B",
	bytes_per_sec: "B/s",
	bits_per_sec: "bps",
	bits: "bit",
};

/** A readable label for each unit value (used in option lists / legends). */
export const METRIC_UNIT_LABELS: Record<MetricUnit, string> = {
	none: "none (raw)",
	bytes: "bytes",
	bytes_per_sec: "bytes/sec",
	bits_per_sec: "bits/sec",
	bits: "bits",
	percent: "percent",
	seconds: "seconds",
};

export const METRIC_SCALE_LABELS: Record<MetricScale, string> = {
	auto: "auto",
	k: `k (×${BASE})`,
	m: `M (×${BASE ** 2})`,
	g: `G (×${BASE ** 3})`,
	t: `T (×${BASE ** 4})`,
};

export interface ScaleInfo {
	divisor: number;
	suffix: string;
}

/**
 * Pick a (divisor, suffix) for the given magnitude, unit, and scale mode.
 * `maxAbs` is the reference value used for auto-scaling (typically the chart's
 * max absolute value) so the whole axis uses one consistent prefix.
 */
export function metricScaleInfo(
	maxAbs: number,
	unit: MetricUnit,
	scale: MetricScale,
): ScaleInfo {
	if (unit === "none") return { divisor: 1, suffix: "" };
	if (unit === "percent") return { divisor: 1, suffix: "%" };
	if (unit === "seconds") return { divisor: 1, suffix: "s" };

	const unitSuffix = UNIT_SUFFIX[unit] ?? "";
	let idx: number;
	if (scale === "auto") {
		idx = maxAbs > 0 ? Math.floor(Math.log(maxAbs) / Math.log(BASE)) : 0;
	} else {
		idx = FIXED_INDEX[scale];
	}
	idx = Math.max(0, Math.min(idx, PREFIXES.length - 1));
	const divisor = BASE ** idx;
	const suffix = `${PREFIXES[idx]}${unitSuffix}`;
	return { divisor, suffix };
}

/** Format a number with up to ~3 significant digits, trimming trailing zeros. */
function trim(n: number): string {
	if (!Number.isFinite(n)) return "—";
	const abs = Math.abs(n);
	if (abs === 0) return "0";
	let s: string;
	if (abs >= 100) s = n.toFixed(0);
	else if (abs >= 10) s = n.toFixed(1);
	else if (abs >= 1) s = n.toFixed(2);
	else s = n.toFixed(3);
	return s.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
}

/** Humanize seconds into a short duration string. */
function formatDuration(seconds: number): string {
	if (!Number.isFinite(seconds)) return "—";
	if (seconds < 1) return `${(seconds * 1000).toFixed(0)} ms`;
	if (seconds < 60) return `${seconds.toFixed(1)} s`;
	if (seconds < 3600) return `${(seconds / 60).toFixed(1)} min`;
	if (seconds < 86400) return `${(seconds / 3600).toFixed(1)} h`;
	return `${(seconds / 86400).toFixed(1)} d`;
}

/**
 * Format a value using a precomputed ScaleInfo (so the tooltip matches the axis).
 * percent/seconds ignore the divisor and format specially.
 */
export function formatScaled(
	value: number | null | undefined,
	{ divisor, suffix }: ScaleInfo,
	unit: MetricUnit,
): string {
	if (value == null || Number.isNaN(value)) return "—";
	if (unit === "percent") return `${trim(value)}%`;
	if (unit === "seconds") return formatDuration(value);
	return `${trim(value / divisor)} ${suffix}`.trim();
}

/** One-shot format (picks its own scale from the value itself). Handy for scalars. */
export function formatMetricValue(
	value: number | null | undefined,
	unit: MetricUnit,
	scale: MetricScale = "auto",
): string {
	if (value == null || Number.isNaN(value)) return "—";
	const info = metricScaleInfo(Math.abs(value), unit, scale);
	return formatScaled(value, info, unit);
}
