/**
 * ConflictTarget — runtime-backed identity of a conflict's structural target.
 *
 * Identifies what entity, edge, or property a conflict is about. Carries a
 * content-addressed `targetDigest` for grouping and deduplication.
 *
 * @module domain/types/conflict/ConflictTarget
 */
import WarpError from "../../errors/WarpError.js";
const VALID_TARGET_KINDS = new Set(['node', 'edge', 'node_property', 'edge_property']);
const SELECTOR_FIELDS = Object.freeze(['entityId', 'propertyKey', 'from', 'to', 'label']);
/**
 * Validates that the given value is a recognized target kind.
 */
function validateTargetKind(kind) {
    if (!VALID_TARGET_KINDS.has(kind)) {
        throw new WarpError(`ConflictTarget: targetKind must be one of ${[...VALID_TARGET_KINDS].join(', ')}`, 'E_VALIDATION');
    }
}
/**
 * Tests whether all specified selector fields match the given target.
 */
function selectorFieldsMatch(target, selector) {
    for (const field of SELECTOR_FIELDS) {
        const selectorValue = selector[field];
        if (selectorValue !== undefined && target[field] !== selectorValue) {
            return false;
        }
    }
    return true;
}
/**
 * Validates that a string value is non-empty.
 */
function requireNonEmptyString(value, name) {
    if (typeof value !== 'string' || value.length === 0) {
        throw new WarpError(`ConflictTarget: ${name} must be a non-empty string`, 'E_VALIDATION');
    }
    return value;
}
/**
 * Validates an optional string field — must be a non-empty string or absent.
 */
function optionalString(value, name) {
    if (value === undefined || value === null) {
        return undefined;
    }
    if (typeof value !== 'string' || value.length === 0) {
        throw new WarpError(`ConflictTarget: ${name} must be a non-empty string when provided`, 'E_VALIDATION');
    }
    return value;
}
/**
 * A runtime-backed identity for a conflict's structural target.
 *
 * Describes which entity, edge, or property is contested, along with
 * a content-addressed digest for deterministic grouping.
 *
 * Instances are frozen on construction. All invariants are validated eagerly.
 */
export default class ConflictTarget {
    targetKind;
    targetDigest;
    entityId;
    propertyKey;
    from;
    to;
    label;
    edgeKey;
    /**
     * Creates a new ConflictTarget with validated fields.
     */
    constructor({ targetKind, targetDigest, entityId, propertyKey, from, to, label, edgeKey }) {
        validateTargetKind(targetKind);
        this.targetKind = targetKind;
        this.targetDigest = requireNonEmptyString(targetDigest, 'targetDigest');
        this.entityId = optionalString(entityId, 'entityId');
        this.propertyKey = optionalString(propertyKey, 'propertyKey');
        this.from = optionalString(from, 'from');
        this.to = optionalString(to, 'to');
        this.label = optionalString(label, 'label');
        this.edgeKey = optionalString(edgeKey, 'edgeKey');
        Object.freeze(this);
    }
    /**
     * Tests whether this target references the given entity by id, source, or destination.
     */
    touchesEntity(entityId) {
        if (this.entityId === entityId) {
            return true;
        }
        return this.from === entityId || this.to === entityId;
    }
    /**
     * Tests whether this target matches a user-supplied target selector filter.
     *
     * A null or undefined selector matches all targets.
     */
    matchesSelector(selector) {
        if (selector === undefined || selector === null) {
            return true;
        }
        if (this.targetKind !== selector.targetKind) {
            return false;
        }
        return selectorFieldsMatch(this, selector);
    }
}
