const codeToType = {
	0: Int8Array,
	1: Uint8Array,
	2: Int16Array,
	3: Uint16Array,
	4: Int32Array,
	5: Uint32Array,
	7: Float32Array,
	8: Float64Array,
	9: BigInt64Array,
	10: BigUint64Array
};

const typeToBytes = {
	Int8Array: 1,
	Uint8Array: 1,
	Int16Array: 2,
	Uint16Array: 2,
	Int32Array: 4,
	Uint32Array: 4,
	Float32Array: 4,
	Float64Array: 8,
	BigInt64Array: 8,
	BigUint64Array: 8
};

function sequential(binary) {
	if (!(binary instanceof Uint8Array) || binary.length < 4) {
		throw new Error('Invalid binary input');
	}

	const view = new DataView(binary.buffer, binary.byteOffset, binary.byteLength);
	let offset = 0;

	// Initialize result (assuming single i value for simplicity; extend for multiple i values if needed)
	const result = { i: null, j: [], Δelems: [], elems: [] };

	// Process bundles
	while (offset < binary.length) {
		// Read bundle header
		if (offset + 4 > binary.length) throw new Error('Incomplete bundle header');

		const bundleHeader = view.getUint32(offset, true);
		if ((bundleHeader & 0xFF) !== 0x1C) throw new Error('Invalid bundle marker');
		const bundleLength = bundleHeader >> 8;
		offset += 4;
		const bundleEnd = offset + bundleLength;

		if (bundleEnd > binary.length) throw new Error('Bundle length exceeds input size');

		// Process chunks in bundle
		while (offset < bundleEnd) {
			// Read chunk header
			if (offset + 12 > bundleEnd) throw new Error('Incomplete chunk header');
			const chunkType = view.getUint8(offset);
			if (chunkType !== 0x11) throw new Error(`Unsupported chunk type: ${chunkType}`);
			offset += 1; // Skip udv
			offset += 1;
			const count = view.getUint16(offset, true); offset += 2;
			if (count > 65535) throw new Error('Chunk count exceeds 65535');
			const iValue = view.getUint16(offset, true); offset += 2;
			const j0 = view.getUint16(offset, true); offset += 2;
			const Δj = view.getInt16(offset, true); offset += 2;
			const ΔelemCount = view.getUint8(offset++); // Δelem_count
			const elemCount = view.getUint8(offset++); // elem_count

			// Set i value (assuming all chunks share the same i)
			if (result.i === null) result.i = iValue;
			else if (result.i !== iValue) throw new Error('Multiple i values not supported');

			// Read preface (element types)
			const ΔelemTypes = [];
			for (let i = 0; i < ΔelemCount; i++) {
				if (offset >= bundleEnd) throw new Error('Incomplete Δelem types');
				const typeByte = view.getUint8(offset++);
				const baseCode = typeByte & 0x0F;
				const incrCode = typeByte >> 4;
				if (!codeToType[baseCode] || !codeToType[incrCode]) {
					throw new Error(`Invalid type code in Δelem: ${typeByte}`);
				}
				ΔelemTypes.push({ baseType: codeToType[baseCode], incrType: codeToType[incrCode] });
			}
			const elemTypes = [];
			for (let i = 0; i < elemCount; i++) {
				if (offset >= bundleEnd) throw new Error('Incomplete elem types');
				const typeCode = view.getUint8(offset++);
				if (!codeToType[typeCode]) throw new Error(`Invalid type code in elem: ${typeCode}`);
				elemTypes.push(codeToType[typeCode]);
			}

			// Initialize Δelems and elems arrays if first chunk
			if (!result.Δelems.length && ΔelemCount > 0) {
				result.Δelems = Array(ΔelemCount).fill().map(() => []);
			}
			if (!result.elems.length && elemCount > 0) {
				result.elems = Array(elemCount).fill().map(() => []);
			}

			// Read initial values for Δelems
			const initialValues = [];
			for (const { baseType } of ΔelemTypes) {
				if (offset + typeToBytes[baseType.name] > bundleEnd) {
					throw new Error('Incomplete initial values');
				}
				initialValues.push(readTypedValue(view, offset, baseType));
				offset += typeToBytes[baseType.name];
			}
			// Skip padding
			while (offset % 4 !== 0) {
				if (offset >= bundleEnd) throw new Error('Incomplete padding after initial values');
				offset++;
			}

			// Reconstruct j values
			for (let idx = 0; idx < count; idx++) {
				result.j.push(j0 + idx * Δj);
			}

			// Read record data (non-interleaved)
			for (let i = 0; i < ΔelemCount; i++) {
				let current = initialValues[i];
				const values = result.Δelems[i];
				const incrType = ΔelemTypes[i].incrType;
				const isBigInt = typeof current === 'bigint';
				for (let idx = 0; idx < count; idx++) {
					if (offset + typeToBytes[incrType.name] > bundleEnd) {
						throw new Error('Incomplete Δelem data');
					}
					let delta = readTypedValue(view, offset, incrType);
					if (idx === 0) {
						values.push(isBigInt ? Number(current) : current);
					} else {
						if (isBigInt) {
							delta = BigInt(delta);
							current += delta;
							values.push(Number(current));
						} else {
							current += delta;
							values.push(current);
						}
					}
					offset += typeToBytes[incrType.name];
				}
			}
			for (let i = 0; i < elemCount; i++) {
				const values = result.elems[i];
				const type = elemTypes[i];
				const isBigInt = type === BigInt64Array || type === BigUint64Array;
				for (let idx = 0; idx < count; idx++) {
					if (offset + typeToBytes[type.name] > bundleEnd) {
						throw new Error('Incomplete elem data');
					}
					let value = readTypedValue(view, offset, type);
					values.push(isBigInt ? Number(value) : value);
					offset += typeToBytes[type.name];
				}
			}
			// Skip padding
			while (offset % 4 !== 0) {
				if (offset >= bundleEnd) throw new Error('Incomplete padding after record data');
				offset++;
			}
		}
	}

	return result;
}


function interleaved(binary) {
	if (!(binary instanceof Uint8Array) || binary.length < 4) {
		throw new Error('Invalid binary input');
	}

	const view = new DataView(binary.buffer, binary.byteOffset, binary.byteLength);
	let offset = 0;

	// Initialize result (assuming single i value for simplicity; extend for multiple i values if needed)
	const result = { i: null, j: [], Δelems: [], elems: [] };

	// Process bundles
	while (offset < binary.length) {
		// Read bundle header
		if (offset + 4 > binary.length) throw new Error('Incomplete bundle header');

		const bundleHeader = view.getUint32(offset, true);
		if ((bundleHeader & 0xFF) !== 0x1C) throw new Error('Invalid bundle marker');
		const bundleLength = bundleHeader >> 8;
		offset += 4;
		const bundleEnd = offset + bundleLength;

		if (bundleEnd > binary.length) throw new Error('Bundle length exceeds input size');

		// Process chunks in bundle
		while (offset < bundleEnd) {
			// Read chunk header
			if (offset + 12 > bundleEnd) throw new Error('Incomplete chunk header');
			const chunkType = view.getUint8(offset);
			if (chunkType !== 0x12) throw new Error(`Unsupported chunk type: ${chunkType}`);
			offset += 1; // Skip udv
			offset += 1;
			const count = view.getUint16(offset, true); offset += 2;
			if (count > 65535) throw new Error('Chunk count exceeds 65535');
			const iValue = view.getUint16(offset, true); offset += 2;
			const j0 = view.getUint16(offset, true); offset += 2;
			const Δj = view.getInt16(offset, true); offset += 2;
			const ΔelemCount = view.getUint8(offset++); // Δelem_count
			const elemCount = view.getUint8(offset++); // elem_count

			// Set i value (assuming all chunks share the same i)
			if (result.i === null) result.i = iValue;
			else if (result.i !== iValue) throw new Error('Multiple i values not supported');

			// Read preface (element types)
			const ΔelemTypes = [];
			for (let i = 0; i < ΔelemCount; i++) {
				if (offset >= bundleEnd) throw new Error('Incomplete Δelem types');
				const typeByte = view.getUint8(offset++);
				const baseCode = typeByte & 0x0F;
				const incrCode = typeByte >> 4;
				if (!codeToType[baseCode] || !codeToType[incrCode]) {
					throw new Error(`Invalid type code in Δelem: ${typeByte}`);
				}
				ΔelemTypes.push({ baseType: codeToType[baseCode], incrType: codeToType[incrCode] });
			}
			const elemTypes = [];
			for (let i = 0; i < elemCount; i++) {
				if (offset >= bundleEnd) throw new Error('Incomplete elem types');
				const typeCode = view.getUint8(offset++);
				if (!codeToType[typeCode]) throw new Error(`Invalid type code in elem: ${typeCode}`);
				elemTypes.push(codeToType[typeCode]);
			}

			// Initialize Δelems and elems arrays if first chunk
			if (!result.Δelems.length && ΔelemCount > 0) {
				result.Δelems = Array(ΔelemCount).fill().map(() => []);
			}
			if (!result.elems.length && elemCount > 0) {
				result.elems = Array(elemCount).fill().map(() => []);
			}

			// Read initial values for Δelems
			const initialValues = [];
			for (const { baseType } of ΔelemTypes) {
				if (offset + typeToBytes[baseType.name] > bundleEnd) {
					throw new Error('Incomplete initial values');
				}
				initialValues.push(readTypedValue(view, offset, baseType));
				offset += typeToBytes[baseType.name];
			}
			// Skip padding
			while (offset % 4 !== 0) {
				if (offset >= bundleEnd) throw new Error('Incomplete padding after initial values');
				offset++;
			}

			// Reconstruct j values
			for (let idx = 0; idx < count; idx++) {
				result.j.push(j0 + idx * Δj);
			}

			// Read interleaved record data
			for (let idx = 0; idx < count; idx++) {
				// Read Δelems
				for (let i = 0; i < ΔelemCount; i++) {
					const values = result.Δelems[i];
					const incrType = ΔelemTypes[i].incrType;
					const isBigInt = typeof initialValues[i] === 'bigint';
					if (offset + typeToBytes[incrType.name] > bundleEnd) {
						throw new Error('Incomplete Δelem data');
					}
					let delta = readTypedValue(view, offset, incrType);
					offset += typeToBytes[incrType.name];
					if (idx === 0) {
						values.push(isBigInt ? Number(initialValues[i]) : initialValues[i]);
					} else {
						if (isBigInt) {
							delta = BigInt(delta);
							initialValues[i] += delta;
							values.push(Number(initialValues[i]));
						} else {
							initialValues[i] += delta;
							values.push(initialValues[i]);
						}
					}
				}
				// Read elems
				for (let i = 0; i < elemCount; i++) {
					const values = result.elems[i];
					const type = elemTypes[i];
					const isBigInt = type === BigInt64Array || type === BigUint64Array;
					if (offset + typeToBytes[type.name] > bundleEnd) {
						throw new Error('Incomplete elem data');
					}
					let value = readTypedValue(view, offset, type);
					values.push(isBigInt ? Number(value) : value);
					offset += typeToBytes[type.name];
				}
			}
			// Skip padding
			while (offset % 4 !== 0) {
				if (offset >= bundleEnd) throw new Error('Incomplete padding after record data');
				offset++;
			}
		}
	}

	return result;
}

function readTypedValue(view, offset, type) {
	switch (type) {
		case Int8Array: return view.getInt8(offset);
		case Uint8Array: return view.getUint8(offset);
		case Int16Array: return view.getInt16(offset, true);
		case Uint16Array: return view.getUint16(offset, true);
		case Int32Array: return view.getInt32(offset, true);
		case Uint32Array: return view.getUint32(offset, true);
		case Float32Array: return view.getFloat32(offset, true);
		case Float64Array: return view.getFloat64(offset, true);
		case BigInt64Array: return view.getBigInt64(offset, true);
		case BigUint64Array: return view.getBigUint64(offset, true);
		default: throw new Error(`Unsupported type: ${type.name}`);
	}
}

module.exports = { sequential, interleaved };
