import MemoryBudgetError from "../../errors/MemoryBudgetError.js";
import WarpMemoryPool from "../../memory/WarpMemoryPool.js";
import BoundedQueryNodePage from "./BoundedQueryNodePage.js";
/** Cursorized bounded reader for QueryReadModel node streams. */
export default class BoundedQueryNodePageReader {
    _readModel;
    _pool;
    constructor(fields) {
        const validFields = requireReaderFields(fields);
        this._readModel = requireQueryReadModel(validFields.readModel);
        this._pool = requireWarpMemoryPool(validFields.pool);
        Object.freeze(this);
    }
    async readPage(request) {
        const validRequest = requirePageRequest(request);
        const limit = requirePositiveInteger(validRequest.limit, 'limit');
        const start = cursorOffset(validRequest.cursor ?? null);
        const streamRequest = queryNodeStreamRequest(validRequest);
        return await collectPage({
            nodes: this._readModel.nodes(streamRequest),
            pool: this._pool,
            start,
            limit,
        });
    }
}
function queryNodeStreamRequest(request) {
    return Object.freeze({
        pattern: request.pattern,
        select: request.select,
    });
}
async function collectPage(options) {
    const pageNodes = [];
    const leases = [];
    let index = 0;
    let cursor = null;
    try {
        for await (const node of options.nodes) {
            if (index < options.start) {
                index += 1;
                continue;
            }
            if (pageNodes.length >= options.limit) {
                cursor = index.toString();
                break;
            }
            leases.push(options.pool.acquire({ scope: 'query.nodes.page', amount: 1 }));
            pageNodes.push(node);
            index += 1;
        }
        return new BoundedQueryNodePage({ nodes: pageNodes, cursor });
    }
    finally {
        for (const lease of leases) {
            lease.release();
        }
    }
}
function requireReaderFields(fields) {
    if (fields !== null && typeof fields === 'object') {
        return fields;
    }
    throw new MemoryBudgetError('BoundedQueryNodePageReader requires object fields', {
        code: 'E_BOUNDED_QUERY_PAGE_INVALID',
        context: { field: 'fields' },
    });
}
function requireQueryReadModel(value) {
    if (hasQueryReadModelShape(value)) {
        return value;
    }
    throw new MemoryBudgetError('BoundedQueryNodePageReader requires a QueryReadModel', {
        code: 'E_BOUNDED_QUERY_PAGE_INVALID',
        context: { field: 'readModel' },
    });
}
function hasQueryReadModelShape(value) {
    if (value === null || typeof value !== 'object') {
        return false;
    }
    return hasQueryReadModelMembers(value);
}
function hasQueryReadModelMembers(value) {
    return typeof value.stateHash === 'string'
        && typeof value.nodes === 'function'
        && typeof value.neighbors === 'function'
        && typeof value.nodeProps === 'function';
}
function requirePageRequest(request) {
    if (request !== null && typeof request === 'object') {
        return request;
    }
    throw new MemoryBudgetError('Bounded query node page request must be an object', {
        code: 'E_BOUNDED_QUERY_PAGE_INVALID',
        context: { field: 'request' },
    });
}
function cursorOffset(cursor) {
    if (cursor === null) {
        return 0;
    }
    const parsed = Number.parseInt(cursor, 10);
    if (Number.isInteger(parsed) && parsed >= 0 && parsed.toString() === cursor) {
        return parsed;
    }
    throw new MemoryBudgetError('Bounded query node page cursor must be a non-negative integer string', {
        code: 'E_BOUNDED_QUERY_PAGE_INVALID',
        context: { field: 'cursor', value: cursor },
    });
}
function requirePositiveInteger(value, field) {
    if (Number.isInteger(value) && value > 0) {
        return value;
    }
    throw new MemoryBudgetError('Bounded query node page limit must be a positive integer', {
        code: 'E_BOUNDED_QUERY_PAGE_INVALID',
        context: { field, value },
    });
}
function requireWarpMemoryPool(value) {
    if (value instanceof WarpMemoryPool) {
        return value;
    }
    throw new MemoryBudgetError('BoundedQueryNodePageReader requires a WarpMemoryPool', {
        code: 'E_MEMORY_BUDGET_INVALID',
        context: { field: 'pool' },
    });
}
