import EdgeRecord from "../graph/EdgeRecord.js";
import LegacyEdgePropertyKey from "../graph/LegacyEdgePropertyKey.js";
import LegacyPropertyValue from "../graph/LegacyPropertyValue.js";
import VisibleEdgePropertyRecord from "../graph/VisibleEdgePropertyRecord.js";
import WarpError from "../errors/WarpError.js";
import { EDGE_PROP_PREFIX, FIELD_SEPARATOR, encodeEdgeKey, encodeEdgePropKey, } from "./KeyCodec.js";
import { isLegacyEdgePropertyProjectionTarget, } from "./LegacyPropertyProjectionTarget.js";
import WarpState from "./state/WarpState.js";
import { compareEventIds } from "../utils/EventId.js";
import { compareStrings } from "../utils/StringComparison.js";
/** Projects visible legacy edge properties into compatibility records. */
export default class EdgePropertyProjection {
    /** Returns all visible edge property records in deterministic order. */
    static fromState(state) {
        const checkedState = requireWarpState(state);
        const records = [];
        for (const [encodedKey, register] of checkedState.allPropEntries()) {
            const record = edgePropertyRecordForRegister(checkedState, encodedKey, register);
            if (record !== null) {
                records.push(record);
            }
        }
        records.sort(compareEdgePropertyRecords);
        return Object.freeze(records);
    }
    /** Returns visible property records for one edge in deterministic order. */
    static forEdge(state, edge) {
        const checkedState = requireWarpState(state);
        const owner = edgeRecordForProjectionTarget(checkedState, edge);
        if (owner === null) {
            return Object.freeze([]);
        }
        return EdgePropertyProjection.forEdgeRecord(checkedState, owner);
    }
    /** Returns visible property records for one runtime-backed edge owner. */
    static forEdgeRecord(state, owner) {
        const checkedState = requireWarpState(state);
        const checkedOwner = requireEdgeRecord(owner);
        const records = [];
        const ownerKeyPrefix = edgePropertyKeyPrefix(checkedOwner);
        for (const [encodedKey, register] of checkedState.allPropEntries()) {
            if (!encodedKey.startsWith(ownerKeyPrefix)) {
                continue;
            }
            const record = edgePropertyRecordForOwnerRegister({
                state: checkedState,
                owner: checkedOwner,
                encodedKey,
                register,
            });
            if (record !== null) {
                records.push(record);
            }
        }
        records.sort(compareEdgePropertyRecords);
        return Object.freeze(records);
    }
}
/** Requires a runtime-backed WarpState source. */
function requireWarpState(state) {
    if (!(state instanceof WarpState)) {
        throw new WarpError('EdgePropertyProjection source must be a WarpState', 'E_VALIDATION');
    }
    return state;
}
/** Requires a runtime-backed edge record owner. */
function requireEdgeRecord(owner) {
    if (!(owner instanceof EdgeRecord)) {
        throw new WarpError('EdgePropertyProjection owner must be an EdgeRecord', 'E_VALIDATION');
    }
    return owner;
}
/** Resolves a projection target without throwing on public miss carriers. */
function edgeRecordForProjectionTarget(state, edge) {
    const owner = edgeRecordFromProjectionTarget(edge);
    if (owner === null) {
        return null;
    }
    return state.getEdgeRecord(owner.id);
}
/** Builds an edge property record from one legacy state register. */
function edgePropertyRecordForRegister(state, encodedKey, register) {
    const keyParts = decodeVisibleEdgePropertyKey(encodedKey);
    if (keyParts === null) {
        return null;
    }
    const edgeKey = encodeEdgeKey(keyParts.from, keyParts.to, keyParts.label);
    const visibleRegister = visibleEdgeRegister(register, state.edgeBirthEvent.get(edgeKey));
    if (visibleRegister === null) {
        return null;
    }
    const decodedOwner = edgeRecordFromProjectionTarget(keyParts);
    if (decodedOwner === null) {
        return null;
    }
    const owner = state.getEdgeRecord(decodedOwner.id);
    if (owner === null) {
        return null;
    }
    return new VisibleEdgePropertyRecord({
        owner,
        key: new LegacyEdgePropertyKey(keyParts.propKey),
        value: new LegacyPropertyValue(visibleRegister.value),
    });
}
/** Builds an edge property record when it belongs to the requested owner. */
function edgePropertyRecordForOwnerRegister(projection) {
    const keyParts = decodeVisibleEdgePropertyKey(projection.encodedKey);
    if (keyParts === null || !edgePropertyKeyPartsMatchOwner(keyParts, projection.owner)) {
        return null;
    }
    const edgeKey = encodeEdgeKey(keyParts.from, keyParts.to, keyParts.label);
    const visibleRegister = visibleEdgeRegister(projection.register, projection.state.edgeBirthEvent.get(edgeKey));
    if (visibleRegister === null) {
        return null;
    }
    return new VisibleEdgePropertyRecord({
        owner: projection.owner,
        key: new LegacyEdgePropertyKey(keyParts.propKey),
        value: new LegacyPropertyValue(visibleRegister.value),
    });
}
/** Returns true when decoded edge-property key parts belong to an owner record. */
function edgePropertyKeyPartsMatchOwner(keyParts, owner) {
    return keyParts.from === owner.from.toString()
        && keyParts.to === owner.to.toString()
        && keyParts.label === owner.typeId.toString();
}
/** Builds a runtime edge record only for validated legacy projection targets. */
function edgeRecordFromProjectionTarget(edge) {
    if (!isLegacyEdgePropertyProjectionTarget(edge)) {
        return null;
    }
    return EdgeRecord.fromLegacyEdge(edge);
}
/** Returns the exact encoded edge-property prefix for one edge owner. */
function edgePropertyKeyPrefix(owner) {
    return encodeEdgePropKey(owner.from.toString(), owner.to.toString(), owner.typeId.toString(), '');
}
/** Decodes only well-formed legacy edge property keys. */
function decodeVisibleEdgePropertyKey(encodedKey) {
    if (!encodedKey.startsWith(EDGE_PROP_PREFIX)) {
        return null;
    }
    const parts = encodedKey.slice(1).split(FIELD_SEPARATOR);
    return edgePropertyKeyPartsFromSegments(parts);
}
/** Returns edge property parts only when the legacy segments are well-formed. */
function edgePropertyKeyPartsFromSegments(parts) {
    if (parts.length !== 4) {
        return null;
    }
    return edgePropertyKeyPartsFromValues({
        from: parts[0],
        to: parts[1],
        label: parts[2],
        propKey: parts[3],
    });
}
/** Returns edge property parts when all decoded values are non-empty. */
function edgePropertyKeyPartsFromValues(values) {
    if (!hasCompleteEdgePropertyKeySegments(values)) {
        return null;
    }
    const keyParts = {
        from: values.from,
        to: values.to,
        label: values.label,
        propKey: values.propKey,
    };
    if (!isLegacyEdgePropertyProjectionTarget(keyParts)) {
        return null;
    }
    return keyParts;
}
/** Returns true when all edge-property key segments are non-empty. */
function hasCompleteEdgePropertyKeySegments(values) {
    if (!isNonEmptyString(values.from)) {
        return false;
    }
    if (!isNonEmptyString(values.to)) {
        return false;
    }
    if (!isNonEmptyString(values.label)) {
        return false;
    }
    if (!isNonEmptyString(values.propKey)) {
        return false;
    }
    return true;
}
/** Returns true for decoded non-empty string segments. */
function isNonEmptyString(value) {
    return value !== undefined && value.length > 0;
}
/** Filters edge registers hidden by edge rebirth. */
function visibleEdgeRegister(register, birthEvent) {
    if (birthEvent === undefined || register.eventId === null) {
        return register;
    }
    if (compareEventIds(register.eventId, birthEvent) < 0) {
        return null;
    }
    return register;
}
/** Compares edge property records by owner and key. */
function compareEdgePropertyRecords(left, right) {
    return compareStrings(edgePropertyRecordSortKey(left), edgePropertyRecordSortKey(right));
}
/** Returns the deterministic sort key for an edge property record. */
function edgePropertyRecordSortKey(record) {
    return `${record.owner.id.toString()}:${record.key.toString()}`;
}
