// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import crc32 from 'crc-32';

import { FoundationContext } from '../types';

const CHUNK_SIZE = 1024 * 1024; // 1MB chunks

/**
 * Convert a hex string to base64 using the injected `toBase64` from the
 * foundation context.
 */
const hexToBase64 = (hex: string, toBase64: FoundationContext['toBase64']) => {
	const bytes = new Uint8Array(
		(hex.match(/\w{2}/g) ?? []).map(h => parseInt(h, 16)),
	);

	return toBase64(bytes);
};

/**
 * Calculate the CRC32 checksum for given content and return base64 encoded
 * checksum. Environment-specific dependencies (`readFile`, `toBase64`) are
 * injected via the {@link FoundationContext} so the foundation layer stays
 * free of any environment-discriminating logic.
 */
export const calculateContentCRC32 = async (
	ctx: FoundationContext,
	content: Blob | string | ArrayBuffer | ArrayBufferView,
	seed = 0,
): Promise<string> => {
	let internalSeed = seed;

	if (content instanceof ArrayBuffer || ArrayBuffer.isView(content)) {
		let uint8Array: Uint8Array;

		if (content instanceof ArrayBuffer) {
			uint8Array = new Uint8Array(content);
		} else {
			uint8Array = new Uint8Array(
				content.buffer,
				content.byteOffset,
				content.byteLength,
			);
		}

		let offset = 0;
		while (offset < uint8Array.length) {
			const end = Math.min(offset + CHUNK_SIZE, uint8Array.length);
			const chunk = uint8Array.slice(offset, end);
			internalSeed = crc32.buf(chunk, internalSeed) >>> 0;
			offset = end;
		}
	} else {
		let blob: Blob;

		if (content instanceof Blob) {
			blob = content;
		} else {
			blob = new Blob([content]);
		}

		let offset = 0;
		while (offset < blob.size) {
			const end = Math.min(offset + CHUNK_SIZE, blob.size);
			const chunk = blob.slice(offset, end);
			const arrayBuffer = await ctx.readFile(chunk);
			const uint8Array = new Uint8Array(arrayBuffer);

			internalSeed = crc32.buf(uint8Array, internalSeed) >>> 0;

			offset = end;
		}
	}

	const hex = internalSeed.toString(16).padStart(8, '0');

	return hexToBase64(hex, ctx.toBase64);
};
