const b = 1
const kb = b * 1024
const mb = kb * 1024
const gb = mb * 1024
const tb = gb * 1024
const pb = tb * 1024

type SizeUnit =
	| 'Bytes'
	| 'Byte'
	| 'B'
	| 'Kilobytes'
	| 'Kilobyte'
	| 'Kb'
	| 'KB'
	| 'K'
	| 'Megabytes'
	| 'Megabyte'
	| 'Mb'
	| 'MB'
	| 'M'
	| 'Gigabytes'
	| 'Gigabyte'
	| 'Gb'
	| 'GB'
	| 'G'
	| 'Terabytes'
	| 'Terabyte'
	| 'Tb'
	| 'TB'
	| 'T'
	| 'Petabytes'
	| 'Petabyte'
	| 'Pb'
	| 'PB'
	| 'P'

type SizeUnitAnyCase = SizeUnit | Uppercase<SizeUnit> | Lowercase<SizeUnit>

export type SizeStringValue =
	| `${number}`
	| `${number}${SizeUnitAnyCase}`
	| `${number} ${SizeUnitAnyCase}`

/**
 * Парсит строку с размером файла и возвращает размер в байтах
 * @param str - строка с размером, например '2GB', '1.5 MB', '512kb', '1024'
 * @returns размер в байтах
 * 
 * @example
 * bytes('2GB') // вернет 2147483648
 * bytes('1.5 MB') // вернет 1572864
 * bytes('512kb') // вернет 524288
 * bytes('1024') // вернет 1024 (байты по умолчанию)
 */
export const bytes = (str: SizeStringValue): number => {
	if (typeof str !== 'string' || str.length === 0 || str.length > 100) {
		throw new Error(
			'Value provided to bytes() must be a string with length between 1 and 99.'
		)
	}

	const match =
		/^(?<value>-?(?:\d+)?\.?\d+) *(?<type>bytes?|b|kilobytes?|kb|k|megabytes?|mb|m|gigabytes?|gb|g|terabytes?|tb|t|petabytes?|pb|p)?$/i.exec(
			str
		)

	const groups = match?.groups as { value: string; type?: string } | undefined
	if (!groups) {
		return NaN
	}
	const n = parseFloat(groups.value)
	const type = (groups.type || 'b').toLowerCase() as Lowercase<SizeUnit>

	switch (type) {
		case 'petabytes':
		case 'petabyte':
		case 'pb':
		case 'p':
			return n * pb
		case 'terabytes':
		case 'terabyte':
		case 'tb':
		case 't':
			return n * tb
		case 'gigabytes':
		case 'gigabyte':
		case 'gb':
		case 'g':
			return n * gb
		case 'megabytes':
		case 'megabyte':
		case 'mb':
		case 'm':
			return n * mb
		case 'kilobytes':
		case 'kilobyte':
		case 'kb':
		case 'k':
			return n * kb
		case 'bytes':
		case 'byte':
		case 'b':
			return n
		default:
			throw new Error(
				`Error: size unit ${type} was recognized but does not have a corresponding case. Please check the input.`
			)
	}
}