import { projectState } from "./StateSerializer.js";
import { cloneBag, cloneMeta, cloneNeighbors, createEdgeContentMetaIndex, createEdgePropIndex, createEdgePropertyRecords, createNeighborIndex, createNodeContentMetaIndex, createNodePropIndex, createNodePropertyRecords, createProjectionProps, createStateReaderProjectionState, createVisibleEdges, edgeKeyFromRef, populateVisibleEdgeProps, populateVisibleNodeProps, } from "./StateReaderContext.js";
// ── Projection clone ─────────────────────────────────────────────────────────
/** Deep-clones the state projection for safe external consumption. */
function cloneProjection(context) {
    return {
        nodes: [...context.projection.nodes],
        edges: context.projection.edges.map((edge) => ({ ...edge })),
        props: context.projection.props.map((prop) => ({ ...prop })),
    };
}
// ── Context query helpers ────────────────────────────────────────────────────
/** Checks whether a node is visible in the current state. */
function hasVisibleNode(context, nodeId) {
    return context.visibleNodeIds.has(nodeId);
}
/** Returns a copy of all visible node IDs. */
function getVisibleNodes(context) {
    return [...context.projection.nodes];
}
/** Returns cloned visible edge views with properties. */
function getVisibleEdges(context) {
    return context.edges.map((edge) => ({ ...edge, props: cloneBag(edge.props) }));
}
/** Returns cloned properties for a visible node, or null if not visible. */
function getVisibleNodeProps(context, nodeId) {
    if (!hasVisibleNode(context, nodeId)) {
        return null;
    }
    return cloneBag(context.nodePropsById.get(nodeId) ?? Object.freeze({}));
}
/** Returns cloned properties for a visible edge, or null if not found. */
function getVisibleEdgeProps(context, edge) {
    const props = context.edgePropsByKey.get(edgeKeyFromRef(edge));
    return props ? cloneBag(props) : null;
}
/** Returns visible neighbors for a node, optionally filtered by direction and label. */
function getVisibleNeighbors(context, nodeId, options = {}) {
    if (!hasVisibleNode(context, nodeId)) {
        return [];
    }
    const { direction = 'both', edgeLabel = undefined } = options;
    const outgoing = (context.outgoingByNode.get(nodeId) ?? []).filter((entry) => edgeLabel === undefined || entry.label === edgeLabel);
    const incoming = (context.incomingByNode.get(nodeId) ?? []).filter((entry) => edgeLabel === undefined || entry.label === edgeLabel);
    if (direction === 'outgoing') {
        return cloneNeighbors(outgoing);
    }
    if (direction === 'incoming') {
        return cloneNeighbors(incoming);
    }
    return cloneNeighbors([...outgoing, ...incoming]);
}
/** Returns cloned content metadata for a visible node, or null. */
function getVisibleNodeContentMeta(context, nodeId) {
    if (!hasVisibleNode(context, nodeId)) {
        return null;
    }
    return cloneMeta(context.nodeContentMetaById.get(nodeId));
}
/** Returns cloned content metadata for a visible edge, or null. */
function getVisibleEdgeContentMeta(context, edge) {
    return cloneMeta(context.edgeContentMetaByKey.get(edgeKeyFromRef(edge)));
}
/** Inspects a visible node, returning its props, neighbors, and content metadata. */
function inspectVisibleNode(context, nodeId) {
    if (!hasVisibleNode(context, nodeId)) {
        return null;
    }
    return {
        nodeId,
        props: cloneBag(context.nodePropsById.get(nodeId) ?? Object.freeze({})),
        outgoing: cloneNeighbors(context.outgoingByNode.get(nodeId) ?? []),
        incoming: cloneNeighbors(context.incomingByNode.get(nodeId) ?? []),
        content: cloneMeta(context.nodeContentMetaById.get(nodeId)),
    };
}
// ── Reader API assembly ──────────────────────────────────────────────────────
/** Assembles the frozen reader API from a pre-built context. */
function buildReaderApi(context) {
    const reader = {
        project() {
            return cloneProjection(context);
        },
        hasNode(nodeId) {
            return hasVisibleNode(context, nodeId);
        },
        getNodes() {
            return getVisibleNodes(context);
        },
        getEdges() {
            return getVisibleEdges(context);
        },
        getNodeProps(nodeId) {
            return getVisibleNodeProps(context, nodeId);
        },
        getEdgeProps(from, to, label) {
            return getVisibleEdgeProps(context, { from, to, label });
        },
        neighbors(nodeId, direction, edgeLabel) {
            return getVisibleNeighbors(context, nodeId, {
                ...(direction !== undefined ? { direction } : {}),
                ...(edgeLabel !== undefined ? { edgeLabel } : {}),
            });
        },
        getNodeContentMeta(nodeId) {
            return getVisibleNodeContentMeta(context, nodeId);
        },
        getEdgeContentMeta(from, to, label) {
            return getVisibleEdgeContentMeta(context, { from, to, label });
        },
        inspectNode(nodeId) {
            return inspectVisibleNode(context, nodeId);
        },
    };
    return Object.freeze(reader);
}
// ── Context construction ─────────────────────────────────────────────────────
/** Builds the full reader context from materialized state, including all indexes. */
function buildReaderContext(state) {
    const projectionState = createStateReaderProjectionState(state);
    const baseProjection = projectState(projectionState);
    const nodePropertyRecords = createNodePropertyRecords(projectionState);
    const edgePropertyRecords = createEdgePropertyRecords(projectionState);
    const projection = {
        nodes: baseProjection.nodes,
        edges: baseProjection.edges,
        props: createProjectionProps(nodePropertyRecords),
    };
    const visibleNodeIds = new Set(projection.nodes);
    const nodePropsById = createNodePropIndex(projection.nodes);
    const edgePropsByKey = createEdgePropIndex(projection.edges);
    const { outgoingByNode, incomingByNode } = createNeighborIndex(projection.nodes, projection.edges);
    populateVisibleNodeProps(nodePropertyRecords, nodePropsById);
    populateVisibleEdgeProps(edgePropertyRecords, edgePropsByKey);
    return {
        projection,
        visibleNodeIds,
        nodePropsById,
        edgePropsByKey,
        edges: createVisibleEdges(projection.edges, edgePropsByKey),
        outgoingByNode,
        incomingByNode,
        nodeContentMetaById: createNodeContentMetaIndex(projectionState, projection.nodes),
        edgeContentMetaByKey: createEdgeContentMetaIndex(projectionState, projection.edges),
    };
}
// ── Public API ───────────────────────────────────────────────────────────────
/**
 * Creates a substrate-generic reader over a materialized WarpState.
 *
 * The reader exposes stable node/edge/property helpers and an entity-local
 * node inspection view without leaking OR-Set internals to higher layers.
 */
export function createStateReader(state) {
    return buildReaderApi(buildReaderContext(state));
}
