/**
 * ZDDC — shared SHA-256 helpers
 *
 * Attaches to window.zddc.crypto. Must load AFTER shared/zddc.js (which creates
 * the window.zddc object).
 *
 * Exports:
 *   zddc.crypto.sha256Hex(buffer)       → Promise<string>  hex digest of ArrayBuffer/Uint8Array
 *   zddc.crypto.sha256String(str)       → Promise<string>  hex digest of UTF-8 encoded string
 *   zddc.crypto.sha256File(file, onProgress?) → Promise<string>
 *       chunked streaming digest for File/Blob; for files >= 4 MB, streams 2 MB chunks
 *       and invokes onProgress(loaded, total) every ~8 MB.
 *   zddc.crypto.bytesToHex(buffer)      → string  (hex of ArrayBuffer/Uint8Array, no digest)
 *
 * Throws if Web Crypto SubtleCrypto is not available.
 */
(function (root) {
    'use strict';

    if (!root.zddc) {
        throw new Error('shared/hash.js: window.zddc must be loaded first');
    }

    var HASH_CHUNK_SIZE = 2 * 1024 * 1024; // 2 MB

    function requireSubtle() {
        if (!root.crypto || !root.crypto.subtle || typeof root.crypto.subtle.digest !== 'function') {
            throw new Error('Web Crypto SubtleCrypto is required');
        }
    }

    function bytesToHex(buffer) {
        return Array.from(new Uint8Array(buffer), function (byte) {
            return byte.toString(16).padStart(2, '0');
        }).join('');
    }

    async function sha256Hex(buffer) {
        requireSubtle();
        var input = (buffer instanceof Uint8Array) ? buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) : buffer;
        var hash = await root.crypto.subtle.digest('SHA-256', input);
        return bytesToHex(hash);
    }

    async function sha256String(str) {
        requireSubtle();
        var bytes = new TextEncoder().encode(str);
        var hash = await root.crypto.subtle.digest('SHA-256', bytes);
        return bytesToHex(hash);
    }

    async function sha256File(file, onProgress) {
        requireSubtle();
        // Single-shot for small files or environments without ReadableStream
        if (file.size < HASH_CHUNK_SIZE * 2 || typeof file.stream !== 'function') {
            if (onProgress) { onProgress(file.size, file.size); }
            var buf = await file.arrayBuffer();
            var hash = await root.crypto.subtle.digest('SHA-256', buf);
            return bytesToHex(hash);
        }
        // Chunked streaming for large files
        var reader = file.stream().getReader();
        var loaded = 0;
        var chunks = [];
        var yieldCounter = 0;
        while (true) {
            var result = await reader.read();
            if (result.done) { break; }
            chunks.push(result.value);
            loaded += result.value.byteLength;
            yieldCounter++;
            if (onProgress && yieldCounter % 4 === 0) {
                onProgress(loaded, file.size);
                await new Promise(function (r) { setTimeout(r, 0); });
            }
        }
        var total = new Uint8Array(loaded);
        var offset = 0;
        for (var i = 0; i < chunks.length; i++) {
            total.set(chunks[i], offset);
            offset += chunks[i].byteLength;
        }
        var digest = await root.crypto.subtle.digest('SHA-256', total.buffer);
        if (onProgress) { onProgress(file.size, file.size); }
        return bytesToHex(digest);
    }

    root.zddc.crypto = {
        sha256Hex:    sha256Hex,
        sha256String: sha256String,
        sha256File:   sha256File,
        bytesToHex:   bytesToHex,
    };
})(typeof window !== 'undefined' ? window : globalThis);
