/**
 * Git-compatible hashing helpers for the in-memory persistence adapter.
 *
 * Computes Git-format SHA-1 hashes for blobs, trees, and commits so that
 * content addresses are deterministic and debuggable against real Git.
 */
import { concatBytes, hexDecode, textEncode } from "../../domain/utils/bytes.js";
import PersistenceError from "../../domain/errors/PersistenceError.js";
import WarpError from "../../domain/errors/WarpError.js";
// ---------------------------------------------------------------------------
// Input coercion
// ---------------------------------------------------------------------------
/** Converts string or Uint8Array to bytes. */
export function toBytes(data) {
    if (data instanceof Uint8Array) {
        return data;
    }
    if (typeof data === 'string') {
        return textEncode(data);
    }
    throw new WarpError('Expected string or Uint8Array', 'E_INVALID_INPUT');
}
let _nodeCreateHash = null;
let _cryptoProbed = false;
/**
 * Lazily probes for node:crypto on first call. Avoids top-level await
 * which forces the module into async evaluation.
 */
async function probeNodeCrypto() {
    if (_cryptoProbed) {
        return _nodeCreateHash;
    }
    try {
        const nodeCrypto = await import('node:crypto');
        _nodeCreateHash = nodeCrypto.createHash;
        _cryptoProbed = true;
    }
    catch {
        // Browser, non-Node runtime, or test-level module mock: callers decide
        // whether missing ambient hashing is acceptable for their boundary.
        _cryptoProbed = false;
    }
    return _nodeCreateHash;
}
/** Default hash function using node:crypto SHA-1. */
export function defaultHash(data) {
    const createHash = _nodeCreateHash;
    if (createHash === null) {
        throw new WarpError('defaultHash called before node:crypto initialization completed', 'E_HASH_NOT_READY');
    }
    return createHash('sha1').update(data).digest('hex');
}
/**
 * Eagerly kicks off the async crypto probe when no custom hash is provided.
 * Returns a promise that resolves when the probe completes.
 */
export async function initCryptoReady(hash) {
    if (hash !== null && hash !== undefined) {
        return true;
    }
    const createHash = await probeNodeCrypto();
    return createHash !== null;
}
// ---------------------------------------------------------------------------
// Git SHA helpers
// ---------------------------------------------------------------------------
/** Computes a Git blob SHA-1: `SHA1("blob " + len + "\0" + content)`. */
export function hashBlob(hash, content) {
    const header = textEncode(`blob ${content.length}\0`);
    return hash(concatBytes(header, content));
}
/** Builds the binary tree buffer in Git's internal format and hashes it. */
export function hashTree(hash, entries) {
    const sorted = [...entries].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
    const parts = sorted.map(e => {
        const prefix = textEncode(`${e.mode} ${e.path}\0`);
        return concatBytes(prefix, hexDecode(e.oid));
    });
    const body = concatBytes(...parts);
    const header = textEncode(`tree ${body.length}\0`);
    return hash(concatBytes(header, body));
}
/** Builds a Git-style commit string and hashes it. */
export function hashCommit(hash, opts) {
    const lines = [`tree ${opts.treeOid}`];
    for (const p of opts.parents) {
        lines.push(`parent ${p}`);
    }
    lines.push(`author ${opts.author} ${opts.date}`);
    lines.push(`committer ${opts.author} ${opts.date}`);
    lines.push('');
    lines.push(opts.message);
    const bodyBytes = textEncode(lines.join('\n'));
    const header = textEncode(`commit ${bodyBytes.length}\0`);
    return hash(concatBytes(header, bodyBytes));
}
// ---------------------------------------------------------------------------
// mktree parsing
// ---------------------------------------------------------------------------
/** Parses a single mktree-formatted line into mode, path, and oid. */
export function parseMktreeEntry(line) {
    const tabIdx = line.indexOf('\t');
    if (tabIdx === -1) {
        throw new PersistenceError(`Invalid mktree entry (missing tab): ${line}`, PersistenceError.E_MISSING_OBJECT);
    }
    const meta = line.slice(0, tabIdx);
    const path = line.slice(tabIdx + 1);
    const [mode = '', , oid = ''] = meta.split(' ');
    return { mode, path, oid };
}
