import { compareStrings } from "../utils/StringComparison.js";
import GenesisEquivalenceComparisonBasis from "./GenesisEquivalenceComparisonBasis.js";
import GenesisEquivalenceMismatch from "./GenesisEquivalenceMismatch.js";
import GenesisEquivalenceProofFailure from "./GenesisEquivalenceProofFailure.js";
import GenesisEquivalenceProofSuccess from "./GenesisEquivalenceProofSuccess.js";
import GenesisEquivalenceProofSummary from "./GenesisEquivalenceProofSummary.js";
import GenesisEquivalenceReading from "./GenesisEquivalenceReading.js";
import WarpError from "../errors/WarpError.js";
/** Pure comparer for legacy and migrated genesis replay readings. */
export default class GenesisEquivalenceProof {
    /** Compares two observer-visible readings and returns a proof result value. */
    compare(basis, legacyReading, migratedReading) {
        const checkedBasis = requireBasis(basis);
        const checkedLegacy = requireReading(legacyReading, 'legacyReading');
        const checkedMigrated = requireReading(migratedReading, 'migratedReading');
        return compareReadings(checkedBasis, checkedLegacy, checkedMigrated);
    }
}
/** Compares validated readings. */
function compareReadings(basis, legacyReading, migratedReading) {
    const legacyFacts = factsByKey(legacyReading.facts);
    const migratedFacts = factsByKey(migratedReading.facts);
    const mismatches = collectMismatches(legacyReading.facts, migratedFacts)
        .concat(collectExtraMismatches(migratedReading.facts, legacyFacts))
        .sort(compareMismatches);
    const summary = new GenesisEquivalenceProofSummary({
        basis,
        legacyFactCount: legacyReading.facts.length,
        migratedFactCount: migratedReading.facts.length,
        mismatchCount: mismatches.length,
    });
    if (mismatches.length === 0) {
        return new GenesisEquivalenceProofSuccess({ basis, summary });
    }
    return new GenesisEquivalenceProofFailure({ basis, summary, mismatches });
}
/** Collects missing and changed migrated facts for legacy facts. */
function collectMismatches(legacyFacts, migratedFacts) {
    const mismatches = [];
    for (const legacyFact of legacyFacts) {
        const migratedFact = migratedFacts.get(legacyFact.toKey());
        if (migratedFact === undefined) {
            mismatches.push(missingMismatch(legacyFact));
            continue;
        }
        if (legacyFact.value !== migratedFact.value) {
            mismatches.push(changedMismatch(legacyFact, migratedFact));
        }
    }
    return Object.freeze(mismatches);
}
/** Collects migrated facts absent from the legacy reading. */
function collectExtraMismatches(migratedFacts, legacyFacts) {
    const mismatches = [];
    for (const migratedFact of migratedFacts) {
        if (!legacyFacts.has(migratedFact.toKey())) {
            mismatches.push(extraMismatch(migratedFact));
        }
    }
    return Object.freeze(mismatches);
}
/** Builds a missing-fact mismatch. */
function missingMismatch(fact) {
    return new GenesisEquivalenceMismatch({
        kind: 'missing',
        factKind: fact.kind,
        factKey: fact.factKey,
        fieldPath: fact.fieldPath,
        legacyValue: fact.value,
        migratedValue: null,
        boundary: fact.boundary,
    });
}
/** Builds an extra-fact mismatch. */
function extraMismatch(fact) {
    return new GenesisEquivalenceMismatch({
        kind: 'extra',
        factKind: fact.kind,
        factKey: fact.factKey,
        fieldPath: fact.fieldPath,
        legacyValue: null,
        migratedValue: fact.value,
        boundary: fact.boundary,
    });
}
/** Builds a changed-field mismatch. */
function changedMismatch(legacyFact, migratedFact) {
    return new GenesisEquivalenceMismatch({
        kind: 'changed',
        factKind: legacyFact.kind,
        factKey: legacyFact.factKey,
        fieldPath: legacyFact.fieldPath,
        legacyValue: legacyFact.value,
        migratedValue: migratedFact.value,
        boundary: legacyFact.boundary ?? migratedFact.boundary,
    });
}
/** Indexes facts by deterministic identity. */
function factsByKey(facts) {
    const indexed = new Map();
    for (const fact of facts) {
        indexed.set(fact.toKey(), fact);
    }
    return indexed;
}
/** Requires a comparison basis instance. */
function requireBasis(basis) {
    if (!(basis instanceof GenesisEquivalenceComparisonBasis)) {
        throw new WarpError('basis must be a GenesisEquivalenceComparisonBasis', 'E_VALIDATION');
    }
    return basis;
}
/** Requires a reading instance. */
function requireReading(reading, label) {
    if (!(reading instanceof GenesisEquivalenceReading)) {
        throw new WarpError(`${label} must be a GenesisEquivalenceReading`, 'E_VALIDATION');
    }
    return reading;
}
/** Compares mismatches deterministically. */
function compareMismatches(left, right) {
    return compareStrings(left.toKey(), right.toKey());
}
