import { formatNumberForDisplay } from "@/utils/math";

const BYTE_UNITS = ["B", "KB", "MB", "GB", "TB"] as const;

export const STORAGE_HEADROOM_RESERVE_BYTES = 50 * 1024 * 1024;

export interface StorageQuotaStatus {
	quotaBytes: number | null;
	usageBytes: number | null;
	headroomBytes: number | null;
	availableBytes: number | null;
}

export interface StorageCapacityCheckResult {
	canStore: boolean;
	reason: "enough-space" | "insufficient-space" | "estimate-unavailable";
	availableBytes: number | null;
}

function normalizeByteValue({ value }: { value: unknown }): number | null {
	if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
		return null;
	}

	return value;
}

export function formatStorageBytes({ bytes }: { bytes: number }): string {
	if (!Number.isFinite(bytes) || bytes <= 0) {
		return "0 B";
	}

	let value = bytes;
	let unitIndex = 0;

	while (value >= 1024 && unitIndex < BYTE_UNITS.length - 1) {
		value /= 1024;
		unitIndex += 1;
	}

	const precision = value >= 10 || unitIndex === 0 ? 0 : 1;
	return `${formatNumberForDisplay({ value, fractionDigits: precision })} ${BYTE_UNITS[unitIndex]}`;
}

export async function readStorageQuotaStatus(): Promise<StorageQuotaStatus> {
	if (
		typeof navigator === "undefined" ||
		!navigator.storage ||
		typeof navigator.storage.estimate !== "function"
	) {
		return {
			quotaBytes: null,
			usageBytes: null,
			headroomBytes: null,
			availableBytes: null,
		};
	}

	const estimate = await navigator.storage.estimate();
	const quotaBytes = normalizeByteValue({ value: estimate.quota });
	const usageBytes = normalizeByteValue({ value: estimate.usage });

	if (quotaBytes === null || usageBytes === null) {
		return {
			quotaBytes,
			usageBytes,
			headroomBytes: null,
			availableBytes: null,
		};
	}

	const headroomBytes = Math.max(quotaBytes - usageBytes, 0);
	const availableBytes = Math.max(
		headroomBytes - STORAGE_HEADROOM_RESERVE_BYTES,
		0,
	);

	return {
		quotaBytes,
		usageBytes,
		headroomBytes,
		availableBytes,
	};
}

export function evaluateStorageCapacity({
	requiredBytes,
	quotaStatus,
}: {
	requiredBytes: number;
	quotaStatus: StorageQuotaStatus;
}): StorageCapacityCheckResult {
	if (quotaStatus.availableBytes === null) {
		return {
			canStore: true,
			reason: "estimate-unavailable",
			availableBytes: null,
		};
	}

	if (requiredBytes > quotaStatus.availableBytes) {
		return {
			canStore: false,
			reason: "insufficient-space",
			availableBytes: quotaStatus.availableBytes,
		};
	}

	return {
		canStore: true,
		reason: "enough-space",
		availableBytes: quotaStatus.availableBytes,
	};
}

export class StorageQuotaExceededError extends Error {
	requiredBytes: number;

	constructor({ requiredBytes }: { requiredBytes: number }) {
		super(
			`Not enough browser storage to save a ${formatStorageBytes({ bytes: requiredBytes })} file.`,
		);

		this.name = "StorageQuotaExceededError";
		this.requiredBytes = requiredBytes;
	}
}

export function isStorageQuotaExceededError({
	error,
}: {
	error: unknown;
}): boolean {
	if (error instanceof StorageQuotaExceededError) {
		return true;
	}

	if (!(error instanceof Error)) {
		return false;
	}

	return (
		error.name === "QuotaExceededError" ||
		error.name === "NS_ERROR_DOM_QUOTA_REACHED" ||
		error.message.toLowerCase().includes("quota")
	);
}
