/**
 * SHA-256 Hash Utilities for Provenance Integrity Verification
 *
 * This module provides cryptographic hashing functions for the OCR Provenance MCP System.
 * All hashes use the format: 'sha256:' + 64-character lowercase hex string.
 *
 * @module utils/hash
 * @see CS-PROV-002 Hash Computation standard in constitution.md
 */
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
/**
 * Hash prefix used for all SHA-256 hashes in this system
 */
const HASH_PREFIX = 'sha256:';
/**
 * Regular expression for validating hash format
 * Matches: 'sha256:' followed by exactly 64 lowercase hex characters
 */
const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
/**
 * Compute SHA-256 hash of content
 *
 * @param content - String or Buffer to hash
 * @returns Hash in format 'sha256:' + 64-char lowercase hex string
 * @example
 * computeHash('hello')
 * // Returns: 'sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
 *
 * @example
 * computeHash(Buffer.from([0x01, 0x02, 0x03]))
 * // Returns: 'sha256:039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81'
 */
export function computeHash(content) {
    const hash = crypto.createHash('sha256').update(content).digest('hex');
    return HASH_PREFIX + hash;
}
/**
 * Compute SHA-256 hash of a file using streaming (memory efficient)
 *
 * This function reads the file in chunks using a stream, making it suitable
 * for hashing large files without loading them entirely into memory.
 *
 * @param filePath - Absolute path to file
 * @returns Promise resolving to hash in format 'sha256:' + 64-char hex string
 * @throws Error if file doesn't exist, path is not absolute, or can't be read
 *
 * @example
 * const hash = await hashFile('/path/to/document.pdf');
 * // Returns: 'sha256:...'
 */
export async function hashFile(filePath) {
    // Validate path is absolute
    if (!path.isAbsolute(filePath)) {
        throw new Error(`Path must be absolute: ${filePath}`);
    }
    // Check file exists and is accessible
    try {
        await fs.promises.access(filePath, fs.constants.R_OK);
    }
    catch (error) {
        const fsError = error;
        if (fsError.code === 'ENOENT') {
            throw new Error(`File not found: ${filePath}`);
        }
        if (fsError.code === 'EACCES') {
            throw new Error(`Permission denied: ${filePath}`);
        }
        throw new Error(`Cannot access file: ${filePath} - ${fsError.message}`);
    }
    // Verify it's a file, not a directory
    const stats = await fs.promises.stat(filePath);
    if (!stats.isFile()) {
        throw new Error(`Path is not a file: ${filePath}`);
    }
    return new Promise((resolve, reject) => {
        const hash = crypto.createHash('sha256');
        const stream = fs.createReadStream(filePath);
        stream.on('data', (chunk) => {
            hash.update(chunk);
        });
        stream.on('end', () => {
            resolve(HASH_PREFIX + hash.digest('hex'));
        });
        stream.on('error', (error) => {
            stream.destroy();
            if (error.code === 'ENOENT') {
                reject(new Error(`File not found: ${filePath}`));
            }
            else if (error.code === 'EACCES') {
                reject(new Error(`Permission denied: ${filePath}`));
            }
            else {
                reject(new Error(`Error reading file: ${filePath} - ${error.message}`));
            }
        });
    });
}
/**
 * Compute SHA-256 hash of a file synchronously using chunked reads.
 *
 * Reads the file in 64KB chunks to avoid loading the entire file into memory.
 * Suitable for hashing large image files during batch processing.
 *
 * @param filePath - Path to the file to hash
 * @returns Hash in format 'sha256:' + 64-char lowercase hex string
 * @throws Error if file cannot be read
 */
export function computeFileHashSync(filePath) {
    const CHUNK_SIZE = 65536; // 64KB chunks
    const hash = crypto.createHash('sha256');
    const fd = fs.openSync(filePath, 'r');
    try {
        const buffer = Buffer.allocUnsafe(CHUNK_SIZE);
        let bytesRead;
        while ((bytesRead = fs.readSync(fd, buffer, 0, CHUNK_SIZE, null)) > 0) {
            hash.update(bytesRead === CHUNK_SIZE ? buffer : buffer.subarray(0, bytesRead));
        }
    }
    finally {
        fs.closeSync(fd);
    }
    return HASH_PREFIX + hash.digest('hex');
}
/**
 * Validate hash format is correct
 *
 * @param hash - Hash string to validate
 * @returns true if format is 'sha256:' + 64 lowercase hex chars, false otherwise
 *
 * @example
 * isValidHashFormat('sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824')
 * // Returns: true
 *
 * @example
 * isValidHashFormat('sha256:ABC123') // Wrong length, uppercase
 * // Returns: false
 */
export function isValidHashFormat(hash) {
    if (typeof hash !== 'string') {
        return false;
    }
    return HASH_PATTERN.test(hash);
}
//# sourceMappingURL=hash.js.map