import { bytesToHex, hexToBytes } from 'nostr-tools/utils';

const isHex = (hex: string) => /^[0-9a-fA-F]+$/.test(hex) && hex.length % 2 === 0;

export function write_varint(bytes: number[], n: number): number {
	let len = 0;
	while (true) {
		let b = n & 0x7f;
		n >>= 7;
		if (n !== 0) b |= 0x80;
		bytes.push(b);
		len += 1;
		if (n === 0) break;
	}
	return len;
}

export const write_tagged_varint = (bytes: number[], value: number, tagged: boolean): number =>
	write_varint(bytes, (value << 1) | (tagged ? 1 : 0));

export function write_string(bytes: number[], s: string) {
	if (s.length === 0) return write_tagged_varint(bytes, 0, false);

	if (isHex(s)) {
		const parsed = hexToBytes(s);
		write_tagged_varint(bytes, parsed.length, true);
		bytes.push(...parsed);
	} else {
		const contentBytes = new TextEncoder().encode(s);
		write_tagged_varint(bytes, contentBytes.length, false);
		bytes.push(...contentBytes);
	}
}

export const read_tagged_varint = (data: Uint8Array, offset: number): [number, boolean, number] => {
	const [value, bytes_read] = read_varint(data, offset);
	const tagged = (value & 1) === 1;
	const actual_value = value >> 1;
	return [actual_value, tagged, bytes_read];
};

export function read_varint(buffer: Uint8Array, offset: number): [number, number] {
	let value = 0;
	let shift = 0;
	let bytesRead = 0;

	while (offset + bytesRead < buffer.length) {
		const byte = buffer[offset + bytesRead];
		if (!byte) return [value, bytesRead]; // never gonna happen, but typescript is being a bitch
		bytesRead++;

		value |= (byte & 0x7f) << shift;

		if ((byte & 0x80) === 0) break;

		shift += 7;
	}

	return [value, bytesRead];
}

export function read_string(data: Uint8Array, offset: number): [string, number] {
	const [length, tagged, varint_bytes] = read_tagged_varint(data, offset);
	offset += varint_bytes;

	if (length === 0) return ['', varint_bytes];

	const stringData = data.slice(offset, offset + length);

	if (tagged) return [bytesToHex(stringData), varint_bytes + length];
	return [new TextDecoder().decode(stringData), varint_bytes + length];
}
