"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ECSqlSyncReader = void 0;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module ECSQL
 */
const core_common_1 = require("@itwin/core-common");
const core_bentley_1 = require("@itwin/core-bentley");
/**
 * Execute ECSQL statements synchronously and read the results one row at a time.
 *
 * This is the synchronous counterpart of [[ECSqlReader]] from `@itwin/core-common`.
 * It uses [[ECSqlRowExecutor]] directly — each call to [[step]] fetches exactly one row
 * with no internal caching, paging, or offset tracking.
 *
 * The query results are returned one row at a time. The format of the row is dictated by the
 * `rowFormat` specified in the `options` parameter of the constructed ECSqlSyncReader object.
 * Defaults to [[QueryRowFormat.UseECSqlPropertyIndexes]] when no `rowFormat` is defined.
 *
 * There are three primary ways to interact with and read the results:
 * - Iterate using ECSqlSyncReader as a synchronous iterator.
 * - Step manually using [[ECSqlSyncReader.step]].
 * - Capture all results at once using [[ECSqlSyncReader.toArray]].
 *
 * @note When iterating over the results, the current row is a [[QueryRowProxy]] object.
 *       To get the row as a basic JavaScript object, call [[QueryRowProxy.toRow]] on it.
 * @beta
 */
class ECSqlSyncReader extends core_common_1.ECSqlReaderBase {
    _executor;
    query;
    _currentRow;
    _options;
    /** Cached native row-adaptor options — built once and reused for every row. */
    _cachedRowOptions;
    /**
     * @internal
     */
    constructor(_executor, query, param, options) {
        const resolvedOptions = options ?? new core_common_1.QueryOptionsBuilder().getOptions();
        if (typeof resolvedOptions.rowFormat === "undefined")
            resolvedOptions.rowFormat = core_common_1.QueryRowFormat.UseECSqlPropertyIndexes;
        super(resolvedOptions.rowFormat);
        this._executor = _executor;
        this.query = query;
        if (query.trim().length === 0) {
            throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "expecting non-empty ecsql statement");
        }
        this._options = resolvedOptions;
        const serializedParam = param ? param.serialize() : new core_common_1.QueryBinder().serialize();
        // Prepare the statement and bind parameters once — avoids per-row ensureStatementReady overhead.
        const args = Object.keys(serializedParam).length > 0 ? serializedParam : undefined;
        this._executor.prepareAndBind(this.query, args);
        // Build and cache the native row-adaptor options — reused for every step call.
        const valueFormat = this._options.rowFormat === core_common_1.QueryRowFormat.UseJsPropertyNames ? core_common_1.DbValueFormat.JsNames : core_common_1.DbValueFormat.ECSqlNames;
        this._cachedRowOptions = {
            abbreviateBlobs: this._options.abbreviateBlobs ?? false,
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            classIdsToClassNames: this._options.convertClassIdsToClassNames ?? false,
            useJsName: valueFormat === core_common_1.DbValueFormat.JsNames,
            doNotConvertClassIdsToClassNamesWhenAliased: true,
        };
        this._currentRow = undefined;
    }
    /**
     * @internal
     */
    getRowInternal() {
        if (!this._currentRow)
            throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "no current row");
        return this._currentRow;
    }
    /**
     * Reads a single row from the executor. Returns the row data or `undefined` when the
     * result set is exhausted.
     *
     * Uses the fast-path `stepNextRow` method on the executor, which calls `stmt.step()`
     * and `stmt.toRow()` directly — no intermediate request/response objects are allocated per row.
     * @internal
     */
    readRow() {
        if (this._done) {
            return undefined;
        }
        // Fetch metadata once on the first call.
        if (this._props.length === 0) {
            this._props = new core_common_1.PropertyMetaDataMap(this._executor.fetchMetadata(this._cachedRowOptions));
        }
        return this.stepWithRetry();
    }
    /**
     * Steps the cursor with retry logic for transient busy/interrupt conditions.
     * Returns the row data array, or `undefined` when the result set is exhausted.
     * @internal
     */
    stepWithRetry() {
        const result = this._executor.stepNextRow(this._cachedRowOptions);
        if (result) {
            ECSqlSyncReader.replaceBase64WithUint8Array(result);
            return result;
        }
        return undefined; // undefined (done) → undefined
    }
    /**
     * Get the metadata for each column in the query result.
     *
     * If metadata has not been retrieved yet, fetches it directly from the prepared
     * statement. If a step has not yet occurred, executes a single step to ensure
     * the statement cursor is positioned, preserving the row for the next [[step]] call.
     *
     * @returns An array of [[QueryPropertyMetaData]].
     * @beta
     */
    getMetaData() {
        if (this._props.length === 0) {
            this._props = new core_common_1.PropertyMetaDataMap(this._executor.fetchMetadata(this._cachedRowOptions));
        }
        return this._props.properties;
    }
    /**
     * Step to the next row of the query result.
     *
     * Each call executes exactly one step on the underlying statement — there is no
     * internal row caching or offset tracking.
     *
     * @returns `true` if a row can be read from `current`.<br/>
     *          `false` if there are no more rows; i.e., all rows have been stepped through already.
     * @beta
     */
    step() {
        if (this._done) {
            return false;
        }
        this._currentRow = this.readRow();
        if (!this._currentRow) {
            this._done = true;
            return false;
        }
        return true;
    }
    /**
     * Get all remaining rows from the query result.
     *
     * @returns An array of all remaining rows from the query result.
     * @beta
     */
    toArray() {
        const rows = [];
        while (this.step()) {
            rows.push(this.formatCurrentRow());
        }
        return rows;
    }
    /**
     * Accessor for using ECSqlSyncReader as a synchronous iterator.
     *
     * @returns A synchronous iterator over the rows returned by the executed ECSQL query.
     * @beta
     */
    [Symbol.iterator]() {
        return this;
    }
    /**
     * Calls step when called as an iterator.
     *
     * Returns the row alongside a `done` boolean to indicate if there are any more rows for an iterator to step to.
     *
     * @returns An object with the keys: `value` which contains the row and `done` which contains a boolean.
     * @beta
     */
    next() {
        if (this.step()) {
            return {
                done: false,
                value: this.current,
            };
        }
        else {
            return {
                done: true,
                value: this.current,
            };
        }
    }
}
exports.ECSqlSyncReader = ECSqlSyncReader;
//# sourceMappingURL=ECSqlSyncReader.js.map