/**
 * Reads property index shards lazily with LRU caching.
 *
 * Loads `props_XX.cbor` shards on demand via IndexStoragePort.readBlob.
 *
 * @module domain/services/index/PropertyIndexReader
 */
import defaultCodec from "../../utils/defaultCodec.js";
import computeShardKey from "../../utils/shardKey.js";
import LRUCache from "../../utils/LRUCache.js";
import IndexError from "../../errors/IndexError.js";
import { isPropValue } from "../../types/PropValue.js";
function createNullRecord() {
    return {};
}
function isIndexedPropertyBag(value) {
    if (value === null || typeof value !== 'object' || Array.isArray(value)) {
        return false;
    }
    return Object.values(value).every((entry) => isPropValue(entry));
}
function isPropertyShardEntry(value) {
    return Array.isArray(value)
        && value.length === 2
        && typeof value[0] === 'string'
        && isIndexedPropertyBag(value[1]);
}
export default class PropertyIndexReader {
    _storage;
    _codec;
    _indexStore;
    _shardOids;
    _cache;
    constructor(options) {
        const { storage, codec, indexStore, maxCachedShards = 64 } = options ?? {};
        this._storage = storage;
        this._codec = codec ?? defaultCodec;
        this._indexStore = indexStore ?? null;
        this._shardOids = new Map();
        this._cache = new LRUCache(maxCachedShards);
    }
    /**
     * Configures OID mappings for lazy loading.
     */
    setup(shardOids) {
        this._shardOids = new Map(Object.entries(shardOids));
        this._cache.clear();
    }
    /**
     * Returns all properties for a node, or null if not found.
     */
    async getNodeProps(nodeId) {
        const shard = await this._loadShard(nodeId);
        if (!shard) {
            return null;
        }
        return shard[nodeId] ?? null;
    }
    /**
     * Returns a single property value, or undefined.
     */
    async getProperty(nodeId, key) {
        const props = await this.getNodeProps(nodeId);
        if (!props) {
            return undefined;
        }
        return props[key];
    }
    async _loadShard(nodeId) {
        const shardKey = computeShardKey(nodeId);
        const path = `props_${shardKey}.cbor`;
        const cached = this._cache.get(path);
        if (cached !== undefined) {
            return cached;
        }
        const oid = this._resolveOid(path);
        if (oid === null) {
            return null;
        }
        return await this._fetchAndDecode(oid, path);
    }
    _resolveOid(path) {
        const oid = this._shardOids.get(path);
        if (oid === undefined || oid === '') {
            return null;
        }
        if (!this._storage && !this._indexStore) {
            return null;
        }
        return oid;
    }
    async _fetchAndDecode(oid, path) {
        if (this._indexStore) {
            const decoded = await this._indexStore.decodeShard(oid);
            return this._parseShard(decoded, path);
        }
        const storage = this._storage;
        const buffer = await storage.readBlob(oid);
        if (buffer === null || buffer === undefined) {
            throw new IndexError(`PropertyIndexReader: missing blob for OID '${oid}' (${path})`, { code: 'E_INDEX_SHARD_MISSING', context: { oid, path } });
        }
        const decoded = this._codec.decode(buffer);
        return this._parseShard(decoded, path);
    }
    _parseShard(decoded, path) {
        if (!Array.isArray(decoded)) {
            const shape = decoded === null ? 'null' : typeof decoded;
            throw new IndexError(`PropertyIndexReader: invalid shard format for '${path}' (expected array, got ${shape})`, { code: 'E_INDEX_SHARD_MALFORMED', context: { path, shape } });
        }
        const data = createNullRecord();
        for (const entry of decoded) {
            if (!isPropertyShardEntry(entry)) {
                throw new IndexError(`PropertyIndexReader: invalid shard property bag for '${path}'`, { code: 'E_INDEX_SHARD_MALFORMED', context: { path } });
            }
            data[entry[0]] = entry[1];
        }
        this._cache.set(path, data);
        return data;
    }
}
