/**
 * ConflictAnalysisRequest — validated request object for conflict analysis.
 *
 * Owns the boundary parsing and normalization for analysis options so the
 * analyzer service can orchestrate instead of shape-checking raw bags.
 *
 * @module domain/services/strand/ConflictAnalysisRequest
 */
import QueryError from "../../errors/QueryError.js";
const VALID_KINDS = new Set(['supersession', 'eventual_override', 'redundancy']);
const VALID_EVIDENCE_LEVELS = new Set(['summary', 'standard', 'full']);
const VALID_TARGET_KINDS = new Set(['node', 'edge', 'node_property', 'edge_property']);
const TARGET_SELECTOR_FIELDS = ['entityId', 'propertyKey', 'from', 'to', 'label'];
const TARGET_REQUIREMENTS = Object.freeze({
    node: { fields: ['entityId'], message: 'node target selector requires entityId' },
    edge: { fields: ['from', 'to', 'label'], message: 'edge target selector requires from, to, and label' },
    node_property: { fields: ['entityId', 'propertyKey'], message: 'node_property selector requires entityId and propertyKey' },
    edge_property: { fields: ['from', 'to', 'label', 'propertyKey'], message: 'edge_property selector requires from, to, label, and propertyKey' },
});
/**
 * Runtime-backed normalized request for analyzer execution.
 */
export default class ConflictAnalysisRequest {
    lamportCeiling;
    strandId;
    entityId;
    target;
    kinds;
    writerId;
    evidence;
    maxPatches;
    constructor({ lamportCeiling, strandId, entityId, target, kinds, writerId, evidence, maxPatches, }) {
        this.lamportCeiling = lamportCeiling;
        this.strandId = strandId;
        this.entityId = entityId;
        this.target = target === null ? null : Object.freeze({ ...target });
        this.kinds = kinds === null ? null : Object.freeze([...kinds]);
        this.writerId = writerId;
        this.evidence = evidence;
        this.maxPatches = maxPatches;
        Object.freeze(this);
    }
    /**
     * Parses raw user input into a validated request object.
     */
    static from(options) {
        const raw = options ?? {};
        return new ConflictAnalysisRequest({
            lamportCeiling: ConflictAnalysisRequest._normalizeLamportCeiling(raw.at?.lamportCeiling),
            strandId: ConflictAnalysisRequest._normalizeOptionalString('strandId', raw.strandId),
            entityId: ConflictAnalysisRequest._normalizeOptionalString('entityId', raw.entityId),
            target: ConflictAnalysisRequest._normalizeTarget(raw.target),
            kinds: ConflictAnalysisRequest._normalizeKinds(raw.kind),
            writerId: ConflictAnalysisRequest._normalizeOptionalString('writerId', raw.writerId),
            evidence: ConflictAnalysisRequest._normalizeEvidence(raw.evidence),
            maxPatches: ConflictAnalysisRequest._normalizeMaxPatches(raw.scanBudget?.maxPatches),
        });
    }
    /**
     * Reports whether the request resolves through a strand coordinate.
     */
    usesStrandCoordinate() {
        return this.strandId !== null;
    }
    /**
     * Tests whether a conflict trace passes all filters in this request.
     */
    matchesTrace(trace) {
        return (this._passesKindFilter(trace) &&
            this._passesEntityFilter(trace) &&
            this._passesTargetFilter(trace) &&
            this._passesWriterFilter(trace));
    }
    _passesKindFilter(trace) {
        return this.kinds === null || this.kinds.includes(trace.kind);
    }
    _passesEntityFilter(trace) {
        if (typeof this.entityId !== 'string' || this.entityId.length === 0) {
            return true;
        }
        return trace.target.touchesEntity(this.entityId);
    }
    _passesTargetFilter(trace) {
        if (this.target === null || this.target === undefined) {
            return true;
        }
        return trace.target.matchesSelector(this.target);
    }
    _passesWriterFilter(trace) {
        if (typeof this.writerId !== 'string' || this.writerId.length === 0) {
            return true;
        }
        return trace.touchesWriter(this.writerId);
    }
    /**
     * Returns a structural record of the active filters for snapshot hashing.
     */
    toSnapshotFilterRecord() {
        return {
            entityId: this.entityId,
            target: ConflictAnalysisRequest._snapshotTarget(this.target),
            kind: this.kinds,
            writerId: this.writerId,
        };
    }
    static _normalizeOptionalString(field, value) {
        if (value === undefined || value === null) {
            return null;
        }
        if (typeof value !== 'string' || value.length === 0) {
            throw new QueryError(`analyzeConflicts(): ${field} must be a non-empty string when provided`, {
                code: 'unsupported_target_selector',
                context: { [field]: value },
            });
        }
        return value;
    }
    static _normalizeLamportCeiling(lamportCeiling) {
        if (lamportCeiling === undefined || lamportCeiling === null) {
            return null;
        }
        if (!ConflictAnalysisRequest._isValidLamportCeiling(lamportCeiling)) {
            throw new QueryError('analyzeConflicts(): at.lamportCeiling must be a non-negative integer or null', {
                code: 'invalid_coordinate',
                context: { lamportCeiling },
            });
        }
        return lamportCeiling;
    }
    static _normalizeTarget(target) {
        if (target === undefined || target === null) {
            return null;
        }
        if (typeof target !== 'object') {
            throw new QueryError('analyzeConflicts(): target selector must be an object', {
                code: 'unsupported_target_selector',
                context: {},
            });
        }
        const selector = { ...target };
        ConflictAnalysisRequest._validateTarget(selector);
        return selector;
    }
    static _validateTarget(target) {
        if (!VALID_TARGET_KINDS.has(target.targetKind)) {
            throw new QueryError('analyzeConflicts(): target.targetKind is unsupported', {
                code: 'unsupported_target_selector',
                context: { targetKind: target.targetKind },
            });
        }
        const requirement = TARGET_REQUIREMENTS[target.targetKind];
        if (!requirement) {
            return;
        }
        ConflictAnalysisRequest._requireTargetFields(target, requirement.fields, requirement.message);
    }
    static _requireTargetFields(target, fields, message) {
        const valid = fields.every((field) => {
            const val = target[field];
            return typeof val === 'string' && val.length > 0;
        });
        if (!valid) {
            throw new QueryError(`analyzeConflicts(): ${message}`, {
                code: 'unsupported_target_selector',
                context: { target },
            });
        }
    }
    static _normalizeKinds(kind) {
        if (kind === undefined) {
            return null;
        }
        const values = Array.isArray(kind) ? kind : [kind];
        ConflictAnalysisRequest._validateKinds(values, kind);
        return [...new Set(values)].sort();
    }
    static _normalizeEvidence(evidence) {
        const normalized = evidence === undefined || evidence === null ? 'standard' : evidence;
        if (typeof normalized !== 'string' || !VALID_EVIDENCE_LEVELS.has(normalized)) {
            throw new QueryError('analyzeConflicts(): evidence must be summary, standard, or full', {
                code: 'unsupported_target_selector',
                context: { evidence: String(evidence) },
            });
        }
        return normalized;
    }
    static _normalizeMaxPatches(maxPatches) {
        if (maxPatches === undefined || maxPatches === null) {
            return null;
        }
        if (typeof maxPatches !== 'number' ||
            !Number.isInteger(maxPatches) ||
            maxPatches < 1) {
            throw new QueryError('analyzeConflicts(): scanBudget.maxPatches must be a positive integer', {
                code: 'unsupported_target_selector',
                context: { maxPatches },
            });
        }
        return maxPatches;
    }
    static _snapshotTarget(selector) {
        if (selector === null) {
            return null;
        }
        const result = { targetKind: selector.targetKind };
        for (const field of TARGET_SELECTOR_FIELDS) {
            const value = selector[field];
            if (value !== undefined) {
                result[field] = value;
            }
        }
        return result;
    }
    static _isValidLamportCeiling(lamportCeiling) {
        return (typeof lamportCeiling === 'number' &&
            Number.isInteger(lamportCeiling) &&
            lamportCeiling >= 0);
    }
    static _validateKinds(values, kind) {
        if (values.length === 0) {
            throw new QueryError('analyzeConflicts(): kind filter must not be empty', {
                code: 'unsupported_target_selector',
                context: { kind: ConflictAnalysisRequest._kindToContext(kind) },
            });
        }
        for (const value of values) {
            if (typeof value !== 'string' || !VALID_KINDS.has(value)) {
                throw new QueryError('analyzeConflicts(): kind filter contains an unsupported value', {
                    code: 'unsupported_target_selector',
                    context: { kind: ConflictAnalysisRequest._kindToContext(kind) },
                });
            }
        }
    }
    static _kindToContext(kind) {
        if (kind === undefined) {
            return [];
        }
        return Array.isArray(kind) ? [...kind] : [kind];
    }
}
