/**
 * Error classification.
 *
 * Server `result_code`s are downstream-opaque (only `"ok"` is a stable
 * contract — see `docs/ARCHITECTURE.md`), so the client cannot key UX off
 * them. Instead every caught error is collapsed into a small, stable set
 * of client-side kinds, derived from the transport-level signal that IS
 * reliable: HTTP status (`AuthError`), the Connect `Code`, a fetch
 * `TypeError`, or the live `navigator.onLine` flag.
 */

import { ConnectError, Code } from "@connectrpc/connect";
import { AuthError } from "../../api/auth";

/** A stable, user-facing classification of any caught error. */
export type ErrorKind =
	| "offline"
	| "network"
	| "auth"
	| "forbidden"
	| "conflict"
	| "notFound"
	| "rateLimit"
	| "server"
	| "unknown";

function fromHttpStatus(status: number): ErrorKind {
	if (status === 401) return "auth";
	if (status === 403) return "forbidden";
	if (status === 404) return "notFound";
	if (status === 409) return "conflict";
	if (status === 429) return "rateLimit";
	if (status >= 500) return "server";
	return "unknown";
}

function fromConnectCode(code: Code): ErrorKind {
	switch (code) {
		case Code.Unauthenticated:
			return "auth";
		case Code.PermissionDenied:
			return "forbidden";
		case Code.NotFound:
			return "notFound";
		case Code.AlreadyExists:
		case Code.Aborted:
		case Code.FailedPrecondition:
			return "conflict";
		case Code.ResourceExhausted:
			return "rateLimit";
		case Code.Unavailable:
			return "network";
		case Code.Internal:
		case Code.DataLoss:
		case Code.Unknown:
			return "server";
		default:
			return "unknown";
	}
}

function hasNumericStatus(err: unknown): err is { status: number } {
	return (
		typeof err === "object" &&
		err !== null &&
		typeof (err as { status?: unknown }).status === "number"
	);
}

/** Collapse any caught value into a stable {@link ErrorKind}. */
export function classifyError(err: unknown): ErrorKind {
	if (typeof navigator !== "undefined" && navigator.onLine === false) {
		return "offline";
	}
	if (err instanceof AuthError) return fromHttpStatus(err.status);
	if (err instanceof ConnectError) return fromConnectCode(err.code);
	if (err instanceof TypeError && /fetch|network|load failed/i.test(err.message)) {
		return "network";
	}
	if (hasNumericStatus(err)) return fromHttpStatus(err.status);
	return "unknown";
}

const RETRYABLE: ReadonlySet<ErrorKind> = new Set<ErrorKind>([
	"offline",
	"network",
	"server",
]);

/** Whether retrying the same operation could plausibly succeed. */
export function isRetryable(kind: ErrorKind): boolean {
	return RETRYABLE.has(kind);
}
