import LegacyNodePropertyKey from "../graph/LegacyNodePropertyKey.js";
import LegacyPropertyValue from "../graph/LegacyPropertyValue.js";
import NodeRecord from "../graph/NodeRecord.js";
import VisibleNodePropertyRecord from "../graph/VisibleNodePropertyRecord.js";
import WarpError from "../errors/WarpError.js";
import { EDGE_PROP_PREFIX, FIELD_SEPARATOR, encodePropKey, } from "./KeyCodec.js";
import { isLegacyNodePropertyProjectionTarget } from "./LegacyPropertyProjectionTarget.js";
import WarpState from "./state/WarpState.js";
import { compareStrings } from "../utils/StringComparison.js";
/** Projects visible legacy node properties into compatibility records. */
export default class NodePropertyProjection {
    /** Returns all visible node property records in deterministic order. */
    static fromState(state) {
        const checkedState = requireWarpState(state);
        const records = [];
        for (const [encodedKey, register] of checkedState.allPropEntries()) {
            const record = nodePropertyRecordForRegister(checkedState, encodedKey, register);
            if (record !== null) {
                records.push(record);
            }
        }
        records.sort(compareNodePropertyRecords);
        return Object.freeze(records);
    }
    /** Returns visible property records for one node in deterministic order. */
    static forNode(state, nodeId) {
        const checkedState = requireWarpState(state);
        const owner = nodeRecordForProjectionTarget(checkedState, nodeId);
        if (owner === null) {
            return Object.freeze([]);
        }
        return NodePropertyProjection.forNodeRecord(checkedState, owner);
    }
    /** Returns visible property records for one runtime-backed node owner. */
    static forNodeRecord(state, owner) {
        const checkedState = requireWarpState(state);
        const checkedOwner = requireNodeRecord(owner);
        const records = [];
        const ownerKeyPrefix = encodePropKey(checkedOwner.id.toString(), '');
        for (const [encodedKey, register] of checkedState.allPropEntries()) {
            if (!encodedKey.startsWith(ownerKeyPrefix)) {
                continue;
            }
            const record = nodePropertyRecordForOwnerRegister(checkedOwner, encodedKey, register);
            if (record !== null) {
                records.push(record);
            }
        }
        records.sort(compareNodePropertyRecords);
        return Object.freeze(records);
    }
}
/** Requires a runtime-backed WarpState source. */
function requireWarpState(state) {
    if (!(state instanceof WarpState)) {
        throw new WarpError('NodePropertyProjection source must be a WarpState', 'E_VALIDATION');
    }
    return state;
}
/** Requires a runtime-backed node record owner. */
function requireNodeRecord(owner) {
    if (!(owner instanceof NodeRecord)) {
        throw new WarpError('NodePropertyProjection owner must be a NodeRecord', 'E_VALIDATION');
    }
    return owner;
}
/** Resolves a projection target without throwing on public miss carriers. */
function nodeRecordForProjectionTarget(state, nodeId) {
    if (typeof nodeId !== 'string') {
        return state.getNodeRecord(nodeId);
    }
    if (!isLegacyNodePropertyProjectionTarget(nodeId)) {
        return null;
    }
    return state.getNodeRecord(nodeId);
}
/** Builds a node property record from one legacy state register. */
function nodePropertyRecordForRegister(state, encodedKey, register) {
    const keyParts = decodeVisibleNodePropertyKey(encodedKey);
    if (keyParts === null) {
        return null;
    }
    const owner = state.getNodeRecord(keyParts.nodeId);
    if (owner === null) {
        return null;
    }
    return new VisibleNodePropertyRecord({
        owner,
        key: new LegacyNodePropertyKey(keyParts.propKey),
        value: new LegacyPropertyValue(register.value),
    });
}
/** Builds a node property record when it belongs to the requested owner. */
function nodePropertyRecordForOwnerRegister(owner, encodedKey, register) {
    const keyParts = decodeVisibleNodePropertyKey(encodedKey);
    if (keyParts === null || keyParts.nodeId !== owner.id.toString()) {
        return null;
    }
    return new VisibleNodePropertyRecord({
        owner,
        key: new LegacyNodePropertyKey(keyParts.propKey),
        value: new LegacyPropertyValue(register.value),
    });
}
/** Decodes only well-formed legacy node property keys. */
function decodeVisibleNodePropertyKey(encodedKey) {
    if (encodedKey.startsWith(EDGE_PROP_PREFIX)) {
        return null;
    }
    const parts = encodedKey.split(FIELD_SEPARATOR);
    return nodePropertyKeyPartsFromSegments(parts);
}
/** Returns node property parts only when the legacy segments are well-formed. */
function nodePropertyKeyPartsFromSegments(parts) {
    if (parts.length !== 2) {
        return null;
    }
    return nodePropertyKeyPartsFromValues(parts[0], parts[1]);
}
/** Returns node property parts when both decoded values are non-empty. */
function nodePropertyKeyPartsFromValues(nodeId, propKey) {
    if (!isNonEmptyString(nodeId)) {
        return null;
    }
    if (!isNonEmptyString(propKey)) {
        return null;
    }
    return { nodeId, propKey };
}
/** Returns true for decoded non-empty string segments. */
function isNonEmptyString(value) {
    return value !== undefined && value.length > 0;
}
/** Compares node property records by owner and key. */
function compareNodePropertyRecords(left, right) {
    return compareStrings(nodePropertyRecordSortKey(left), nodePropertyRecordSortKey(right));
}
/** Returns the deterministic sort key for a node property record. */
function nodePropertyRecordSortKey(record) {
    return `${record.owner.id.toString()}:${record.key.toString()}`;
}
