/**
 * TtdMergeInspector — deterministic merge protocol builder for TTD.
 *
 * @module domain/services/merge/TtdMergeInspector
 */
import MergeClassificationEvidence from "./MergeClassificationEvidence.js";
import MergeClassifier from "./MergeClassifier.js";
import TtdMergeBranch from "./TtdMergeBranch.js";
import TtdMergeFootprint from "./TtdMergeFootprint.js";
import TtdMergeInspection from "./TtdMergeInspection.js";
import TtdMergeLoweringWitness from "./TtdMergeLoweringWitness.js";
import TtdMergeObstructionWitness from "./TtdMergeObstructionWitness.js";
import { freezeSortedRecord, freezeSortedTexts, requireStringRecord, } from "./TtdMergeValidation.js";
function collectKeys(records) {
    const keys = [];
    for (const record of records) {
        keys.push(...Object.keys(record));
    }
    return freezeSortedTexts(keys, 'objectKeys');
}
function valueAt(fields, key) {
    return fields[key] ?? null;
}
function changedKeys(precursor, branch) {
    const keys = collectKeys([precursor, branch.fields]);
    return keys.filter((key) => valueAt(precursor, key) !== valueAt(branch.fields, key));
}
function overlap(leftKeys, rightKeys) {
    const rightSet = new Set(rightKeys);
    return freezeSortedTexts(leftKeys.filter((key) => rightSet.has(key)), 'overlapKeys');
}
function buildObstructions(input) {
    const witnesses = [];
    for (const key of input.overlapKeys) {
        const leftValue = valueAt(input.left.fields, key);
        const rightValue = valueAt(input.right.fields, key);
        if (leftValue !== rightValue) {
            witnesses.push(new TtdMergeObstructionWitness({
                fieldKey: key,
                precursorValue: valueAt(input.precursor, key),
                leftValue,
                rightValue,
            }));
        }
    }
    return Object.freeze(witnesses);
}
function applyValue(candidate, key, value) {
    if (value === null) {
        delete candidate[key];
        return;
    }
    candidate[key] = value;
}
function applyChanges(candidate, branch, keys) {
    for (const key of keys) {
        applyValue(candidate, key, valueAt(branch.fields, key));
    }
}
function buildCandidateJoin(input) {
    if (input.obstructionCount > 0) {
        return null;
    }
    const candidate = { ...input.precursor };
    applyChanges(candidate, input.left, input.leftChangedKeys);
    applyChanges(candidate, input.right, input.rightChangedKeys);
    return freezeSortedRecord(candidate);
}
function buildLowerings(candidate, obstructionWitnesses) {
    if (candidate !== null) {
        const keyOrder = collectKeys([candidate]);
        return Object.freeze([
            new TtdMergeLoweringWitness({
                surface: 'canonical-json-object',
                basisKeyCount: keyOrder.length,
                conflictKeyCount: 0,
                keyOrder,
            }),
        ]);
    }
    const conflictKeys = obstructionWitnesses.map((witness) => witness.fieldKey);
    return Object.freeze([
        new TtdMergeLoweringWitness({
            surface: 'obstruction-list',
            basisKeyCount: conflictKeys.length,
            conflictKeyCount: conflictKeys.length,
            keyOrder: conflictKeys,
        }),
    ]);
}
function buildObjectMergeComputation(input) {
    const precursor = requireStringRecord(input.precursor, 'precursor');
    const left = new TtdMergeBranch(input.left);
    const right = new TtdMergeBranch(input.right);
    const leftChangedKeys = changedKeys(precursor, left);
    const rightChangedKeys = changedKeys(precursor, right);
    const overlapKeys = overlap(leftChangedKeys, rightChangedKeys);
    const obstructions = buildObstructions({ precursor, left, right, overlapKeys });
    const candidate = buildCandidateJoin({
        precursor,
        left,
        right,
        leftChangedKeys,
        rightChangedKeys,
        obstructionCount: obstructions.length,
    });
    const lowerings = buildLowerings(candidate, obstructions);
    return {
        precursor,
        left,
        right,
        leftChangedKeys,
        rightChangedKeys,
        overlapKeys,
        obstructions,
        candidate,
        lowerings,
        policies: Object.freeze([...(input.policyRequirements ?? [])]),
    };
}
function classifyObjectMerge(computation, classifier) {
    return classifier.classify(new MergeClassificationEvidence({
        sharedPrecursor: true,
        branchFootprintsOverlap: computation.overlapKeys.length > 0,
        candidateJoin: computation.candidate !== null,
        obstructionWitness: computation.obstructions.length > 0,
        loweringWitness: computation.lowerings.length > 0,
        policyRequirement: computation.policies.length > 0,
    }));
}
function buildInspection(computation, classifier) {
    return new TtdMergeInspection({
        domain: 'json-object',
        sharedPrecursor: computation.precursor,
        branches: [computation.left, computation.right],
        footprints: [
            new TtdMergeFootprint({ branchId: computation.left.branchId, changedKeys: computation.leftChangedKeys }),
            new TtdMergeFootprint({ branchId: computation.right.branchId, changedKeys: computation.rightChangedKeys }),
        ],
        overlapKeys: computation.overlapKeys,
        candidateCanonicalJoin: computation.candidate,
        obstructionWitnesses: computation.obstructions,
        loweringWitnesses: computation.lowerings,
        policyRequirements: computation.policies,
        classification: classifyObjectMerge(computation, classifier),
    });
}
export default class TtdMergeInspector {
    classifier;
    constructor(classifier = new MergeClassifier()) {
        this.classifier = classifier;
        Object.freeze(this);
    }
    inspectJsonObject(input) {
        return buildInspection(buildObjectMergeComputation(input), this.classifier);
    }
}
