/**
 * BisectService — binary search over WARP graph history.
 *
 * Given a known-good commit SHA and a known-bad commit SHA on a writer's
 * patch chain, finds the first bad patch via binary search, calling a
 * user-supplied test function at each midpoint.
 *
 * @module domain/services/BisectService
 */
// -- Helpers ------------------------------------------------------------------
function rangeError(message) {
    return { result: 'range-error', message };
}
function found(writerId, entry, steps, totalCandidates) {
    return {
        result: 'found',
        firstBadPatch: entry.sha,
        writerId,
        lamport: entry.patch.lamport,
        steps,
        totalCandidates,
    };
}
function resolveCandidates(patches, good, bad) {
    const goodIdx = patches.findIndex((p) => p.sha === good);
    const badIdx = patches.findIndex((p) => p.sha === bad);
    if (goodIdx === -1 || badIdx === -1) {
        return { error: 'good or bad SHA not found in writer chain' };
    }
    if (goodIdx >= badIdx) {
        return { error: 'good is not an ancestor of bad' };
    }
    return { candidates: patches.slice(goodIdx + 1, badIdx + 1) };
}
// -- Service ------------------------------------------------------------------
export default class BisectService {
    _graph;
    constructor(deps) {
        this._graph = deps.graph;
    }
    /**
     * Runs bisect on a single writer's patch chain.
     *
     * Returns 'found' with the first bad patch, or 'range-error'
     * if the good/bad range is invalid.
     */
    async run(opts) {
        if (opts.good === opts.bad) {
            return rangeError('good and bad SHAs are the same');
        }
        const patches = await this._graph.getWriterPatches(opts.writerId);
        const resolved = resolveCandidates(patches, opts.good, opts.bad);
        if ('error' in resolved) {
            return rangeError(resolved.error);
        }
        const { candidates } = resolved;
        if (candidates.length === 1) {
            return found(opts.writerId, candidates[0], 0, 1);
        }
        const search = await this._binarySearch(candidates, opts.testFn);
        return found(opts.writerId, candidates[search.index], search.steps, candidates.length);
    }
    async _binarySearch(candidates, testFn) {
        let lo = 0;
        let hi = candidates.length - 1;
        let steps = 0;
        while (lo < hi) {
            const mid = Math.floor((lo + hi) / 2);
            const candidate = candidates[mid];
            if (!candidate) {
                break;
            }
            steps++;
            const state = await this._graph.materialize({ ceiling: candidate.patch.lamport });
            const isGood = await testFn(state, candidate.sha);
            if (isGood) {
                lo = mid + 1;
            }
            else {
                hi = mid;
            }
        }
        return { index: lo, steps };
    }
}
