<?php
/**
 * Streaming file hashing.
 *
 * @package LocalGuardScanner
 */

namespace LocalGuardScanner\Detection\Filesystem;

use RuntimeException;

/**
 * Calculates SHA-256 without loading a full file into memory.
 */
final class StreamingHasher {
	const CHUNK_BYTES = 65536;

	/**
	 * @param string              $path     File path.
	 * @param array<string,mixed> $expected Enumerated file identity.
	 * @return string
	 */
	public function sha256( $path, array $expected = array() ) {
		$guard   = new FileIdentityGuard();
		$handle  = $guard->open( $path, $expected );
		$context = hash_init( 'sha256' );
		try {
			while ( ! feof( $handle ) ) {
				$chunk = fread( $handle, self::CHUNK_BYTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
				if ( false === $chunk ) {
					throw new RuntimeException( 'File cannot be read for hashing.' );
				}
				if ( '' === $chunk && ! feof( $handle ) ) {
					throw new RuntimeException( 'File hashing stopped before reaching a safe end.' );
				}
				hash_update( $context, $chunk );
			}
			$guard->assert_stable( $handle, $expected );
		} finally {
			fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
		}
		return hash_final( $context );
	}
}
