/**
 * OpStrategies — the per-op-type dispatch table used by the reducer.
 *
 * Each concrete `OpStrategy` subclass owns the five operations that
 * the reducer needs for a single op type:
 *
 *   1. `validate(op)`    — boundary field-shape checks
 *   2. `mutate(state, op, eventId)` — CRDT mutation (in place)
 *   3. `outcome(state, op, eventId)` — pre-mutation receipt outcome
 *   4. `snapshot(state, op)` — pre-mutation alive/prop snapshot
 *   5. `accumulate(diff, state, op, before)` — post-mutation diff emission
 *
 * The ADR-1 canonical split gives us three property strategies
 * (`PropSetStrategy`, `NodePropSetStrategy`, `EdgePropSetStrategy`)
 * that share mutate / snapshot / accumulate helpers via protected
 * statics on the base class.
 *
 * `OP_STRATEGIES` is the frozen Map used by JoinReducer for dispatch.
 *
 * @module domain/services/OpStrategies
 */
import { compareEventIds } from "../utils/EventId.js";
import { encodeEdgeKey, encodePropKey, encodeEdgePropKey, EDGE_PROP_PREFIX, } from "./KeyCodec.js";
import PatchError from "../errors/PatchError.js";
import OpApplied from "../types/ops/OpApplied.js";
import OpValidator from "./OpValidator.js";
import DiffCalculator from "./DiffCalculator.js";
import OpStrategy from "./OpStrategy.js";
import { validateOpStrategyRegistry } from "./OpStrategyRegistryValidation.js";
import ReceiptBuilder from "./ReceiptBuilder.js";
/**
 * Concrete strategies — one per canonical op type.
 *
 * `OpLike`, `SnapshotBeforeOp`, and the abstract `OpStrategy` base // nosemgrep: ts-no-like-types -- 0025C
 * live in their own files; this module keeps only the concrete
 * strategy set and the registry used for reducer dispatch.
 */
// ===================================================================
// Concrete strategies — one per canonical op type
// ===================================================================
class NodeAddStrategy extends OpStrategy {
    receiptName = 'NodeAdd';
    validate(op) { OpValidator.assertString(op, 'node'); OpValidator.assertDot(op); } // nosemgrep: ts-no-like-types -- 0025C
    mutate(state, op) {
        state.nodeAlive.add(op.node, op.dot);
    }
    outcome(state, op) {
        return ReceiptBuilder.nodeAddOutcome(state.nodeAlive, { node: op.node, dot: op.dot });
    }
    snapshot(state, op) {
        return { nodeWasAlive: state.nodeAlive.contains(op.node) };
    }
    accumulate(diff, state, op, before) {
        if (before.nodeWasAlive !== true && state.nodeAlive.contains(op.node)) {
            diff.nodesAdded.push(op.node);
        }
    }
}
class NodeRemoveStrategy extends OpStrategy {
    receiptName = 'NodeTombstone';
    validate(op) { OpValidator.assertIterable(op, 'observedDots'); } // nosemgrep: ts-no-like-types -- 0025C
    mutate(state, op) {
        const dots = op.observedDots;
        state.nodeAlive.remove(dots instanceof Set ? dots : new Set(dots));
    }
    outcome(state, op) {
        const outcomeOp = {
            observedDots: op.observedDots,
        };
        if (typeof op.node === 'string') {
            outcomeOp.node = op.node;
        }
        return ReceiptBuilder.nodeRemoveOutcome(state.nodeAlive, outcomeOp);
    }
    snapshot(state, op) {
        const rawDots = op.observedDots;
        const nodeDots = rawDots instanceof Set ? rawDots : new Set(rawDots);
        return { aliveBeforeNodes: DiffCalculator.aliveElementsForDots(state.nodeAlive, nodeDots) };
    }
    accumulate(diff, state, _op, before) {
        DiffCalculator.collectNodeRemovals(diff, state, before.aliveBeforeNodes);
    }
}
class EdgeAddStrategy extends OpStrategy {
    receiptName = 'EdgeAdd';
    validate(op) {
        OpValidator.assertString(op, 'from');
        OpValidator.assertString(op, 'to');
        OpValidator.assertString(op, 'label');
        OpValidator.assertDot(op);
    }
    mutate(state, op, eventId) {
        const edgeKey = encodeEdgeKey(op.from, op.to, op.label);
        state.edgeAlive.add(edgeKey, op.dot);
        const prev = state.edgeBirthEvent.get(edgeKey);
        if (prev === undefined || compareEventIds(eventId, prev) > 0) {
            state.edgeBirthEvent.set(edgeKey, eventId);
        }
    }
    outcome(state, op) {
        const edgeKey = encodeEdgeKey(op.from, op.to, op.label);
        return ReceiptBuilder.edgeAddOutcome(state.edgeAlive, { dot: op.dot }, edgeKey);
    }
    snapshot(state, op) {
        const ek = encodeEdgeKey(op.from, op.to, op.label);
        return { edgeWasAlive: state.edgeAlive.contains(ek), edgeKey: ek };
    }
    accumulate(diff, state, op, before) {
        if (before.edgeWasAlive !== true && before.edgeKey !== undefined && state.edgeAlive.contains(before.edgeKey)) {
            diff.edgesAdded.push({ from: op.from, to: op.to, label: op.label });
        }
    }
}
class EdgeRemoveStrategy extends OpStrategy {
    receiptName = 'EdgeTombstone';
    validate(op) { OpValidator.assertIterable(op, 'observedDots'); } // nosemgrep: ts-no-like-types -- 0025C
    mutate(state, op) {
        const dots = op.observedDots;
        state.edgeAlive.remove(dots instanceof Set ? dots : new Set(dots));
    }
    outcome(state, op) {
        const outcomeOp = {
            observedDots: op.observedDots,
        };
        if (typeof op.from === 'string') {
            outcomeOp.from = op.from;
        }
        if (typeof op.to === 'string') {
            outcomeOp.to = op.to;
        }
        if (typeof op.label === 'string') {
            outcomeOp.label = op.label;
        }
        return ReceiptBuilder.edgeRemoveOutcome(state.edgeAlive, outcomeOp);
    }
    snapshot(state, op) {
        const rawEdgeDots = op.observedDots;
        const edgeDots = rawEdgeDots instanceof Set ? rawEdgeDots : new Set(rawEdgeDots);
        return { aliveBeforeEdges: DiffCalculator.aliveElementsForDots(state.edgeAlive, edgeDots) };
    }
    accumulate(diff, state, _op, before) {
        DiffCalculator.collectEdgeRemovals(diff, state, before.aliveBeforeEdges);
    }
}
class NodePropSetStrategy extends OpStrategy {
    receiptName = 'NodePropSet';
    validate(op) {
        OpValidator.assertString(op, 'node');
        OpValidator.assertString(op, 'key');
    }
    mutate(state, op, eventId) {
        OpStrategy._mutateProp(state, {
            propKey: encodePropKey(op.node, op.key),
            eventId,
            value: op.value,
        });
    }
    outcome(state, op, eventId) {
        return ReceiptBuilder.propSetOutcome(state, { node: op.node, key: op.key }, eventId);
    }
    snapshot(state, op) {
        return OpStrategy._snapshotProp(state, encodePropKey(op.node, op.key));
    }
    accumulate(diff, state, op, before) {
        OpStrategy._accumulatePropDiff(diff, state, {
            nodeId: op.node,
            key: op.key,
            before,
        });
    }
}
class EdgePropSetStrategy extends OpStrategy {
    receiptName = 'EdgePropSet';
    validate(op) {
        OpValidator.assertString(op, 'from');
        OpValidator.assertString(op, 'to');
        OpValidator.assertString(op, 'label');
        OpValidator.assertString(op, 'key');
    }
    mutate(state, op, eventId) {
        OpStrategy._mutateProp(state, {
            propKey: encodeEdgePropKey(op.from, op.to, op.label, op.key),
            eventId,
            value: op.value,
        });
    }
    outcome(state, op, eventId) {
        return ReceiptBuilder.edgePropSetOutcome(state, { from: op.from, to: op.to, label: op.label, key: op.key }, eventId);
    }
    snapshot(state, op) {
        return OpStrategy._snapshotProp(state, encodeEdgePropKey(op.from, op.to, op.label, op.key));
    }
    accumulate(diff, state, op, before) {
        OpStrategy._accumulatePropDiff(diff, state, {
            nodeId: encodeEdgeKey(op.from, op.to, op.label),
            key: op.key,
            before,
        });
    }
}
class PropSetStrategy extends OpStrategy {
    receiptName = 'PropSet';
    validate(op) {
        OpValidator.assertString(op, 'node');
        OpValidator.assertString(op, 'key');
    }
    mutate(state, op, eventId) {
        // Legacy raw PropSet — must NOT carry edge-property encoding at this point.
        if (typeof op.node === 'string' && op.node[0] === EDGE_PROP_PREFIX) {
            throw new PatchError('Unnormalized legacy edge-property PropSet reached canonical apply path. ' +
                'Call normalizeRawOp() at the decode boundary.', { context: { opType: 'PropSet', node: op.node } });
        }
        OpStrategy._mutateProp(state, {
            propKey: encodePropKey(op.node, op.key),
            eventId,
            value: op.value,
        });
    }
    outcome(state, op, eventId) {
        return ReceiptBuilder.propSetOutcome(state, { node: op.node, key: op.key }, eventId);
    }
    snapshot(state, op) {
        return OpStrategy._snapshotProp(state, encodePropKey(op.node, op.key));
    }
    accumulate(diff, state, op, before) {
        OpStrategy._accumulatePropDiff(diff, state, {
            nodeId: op.node,
            key: op.key,
            before,
        });
    }
}
class BlobValueStrategy extends OpStrategy {
    receiptName = 'BlobValue';
    validate(_op) { } // nosemgrep: ts-no-like-types -- 0025C
    mutate(_state, _op) { } // nosemgrep: ts-no-like-types -- 0025C
    outcome(_state, op) {
        const blobOid = op.oid;
        const blobTarget = (typeof blobOid === 'string' && blobOid.length > 0) ? blobOid : '*';
        return new OpApplied(blobTarget);
    }
    snapshot(_state, _op) { return {}; } // nosemgrep: ts-no-like-types -- 0025C
    accumulate(_diff, _state, _op, _before) { } // nosemgrep: ts-no-like-types -- 0025C
}
/**
 * Frozen registry mapping canonical op types to their strategy instances.
 * Adding a new op type means creating a new `OpStrategy` subclass and
 * registering it here.
 */
export const OP_STRATEGIES = Object.freeze(new Map([
    ['NodeAdd', new NodeAddStrategy()],
    ['NodeRemove', new NodeRemoveStrategy()],
    ['EdgeAdd', new EdgeAddStrategy()],
    ['EdgeRemove', new EdgeRemoveStrategy()],
    ['NodePropSet', new NodePropSetStrategy()],
    ['EdgePropSet', new EdgePropSetStrategy()],
    ['PropSet', new PropSetStrategy()],
    ['BlobValue', new BlobValueStrategy()],
]));
validateOpStrategyRegistry(OP_STRATEGIES);
