/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module SQLiteDb
 */
import { DbChangeStage, DbOpcode, DbResult } from "@itwin/core-bentley";
import { ECDb } from "./ECDb";
import { IModelNative } from "./internal/NativePlatform";
import { _nativeDb } from "./internal/Symbols";
/**
 * Read raw sqlite changeset from disk and enumerate changes.
 * It also optionally let you format change with schema from
 * a db provided.
 * @beta
 */
export class SqliteChangesetReader {
    db;
    _nativeReader = new IModelNative.platform.SqliteChangesetReader();
    _schemaCache = new Map();
    _disableSchemaCheck = false;
    _changeIndex = 0;
    constructor(
    /** db from where sql schema will be read */
    db) {
        this.db = db;
    }
    /**
     * Open changeset file from disk
     * @param args fileName of changeset reader and other options.
     * @returns SqliteChangesetReader instance
     */
    static openFile(args) {
        const reader = new SqliteChangesetReader(args.db);
        reader._disableSchemaCheck = args.disableSchemaCheck ?? false;
        reader._nativeReader.openFile(args.fileName, args.invert ?? false);
        return reader;
    }
    /**
     * Group changeset file into single changeset and open that changeset.
     * @param args - The arguments for opening the changeset group. Requires an open db.
     * @returns The SqliteChangesetReader instance.
     */
    static openGroup(args) {
        if (args.changesetFiles.length === 0) {
            throw new Error("changesetFiles must contain at least one file.");
        }
        const reader = new SqliteChangesetReader(args.db);
        reader._disableSchemaCheck = args.disableSchemaCheck ?? false;
        reader._nativeReader.openGroup(args.changesetFiles, args.db[_nativeDb], args.invert ?? false);
        return reader;
    }
    /**
     * Open txn change in iModel.
     * @param args iModel and other options.
     * @returns SqliteChangesetReader instance
     */
    static openTxn(args) {
        if (args.db instanceof ECDb) {
            throw new Error("ECDb does not support openTxn");
        }
        const reader = new SqliteChangesetReader(args.db);
        reader._disableSchemaCheck = args.disableSchemaCheck ?? false;
        reader._nativeReader.openTxn(args.db[_nativeDb], args.txnId, args.invert ?? false);
        return reader;
    }
    /**
     * Open in-memory changes for the given iModel.
     * @param args - The arguments for opening in-memory changes.
     * @returns SqliteChangesetReader instance
     */
    static openInMemory(args) {
        const reader = new SqliteChangesetReader(args.db);
        reader._disableSchemaCheck = args.disableSchemaCheck ?? false;
        reader._nativeReader.openInMemoryChanges(args.db[_nativeDb], args.invert ?? false);
        return reader;
    }
    /**
     * Writes the changeset to a file.
     * @note can be use with openGroup() or openLocalChanges() to persist changeset.
     * @param args - The arguments for writing to the file.
     * @param args.fileName - The name of the file to write to.
     * @param args.containsSchemaChanges - Indicates whether the changeset contains schema changes.
     * @param args.overwriteFile - Indicates whether to override the file if it already exists. Default is false.
     */
    writeToFile(args) {
        this._nativeReader.writeToFile(args.fileName, args.containsSchemaChanges, args.overwriteFile ?? false);
    }
    /**
     * Open local changes in iModel.
     * @param args iModel and other options.
     * @param args.db must be of type IModelDb
     * @returns SqliteChangesetReader instance
     */
    static openLocalChanges(args) {
        const reader = new SqliteChangesetReader(args.db);
        reader._disableSchemaCheck = args.disableSchemaCheck ?? false;
        reader._nativeReader.openLocalChanges(args.db[_nativeDb], args.includeInMemoryChanges ?? false, args.invert ?? false);
        return reader;
    }
    /** check if schema check is disabled or not */
    get disableSchemaCheck() { return this._disableSchemaCheck; }
    /** Move to next change in changeset
     * @returns true if there is current change false if reader is end of changeset.
     * @beta
    */
    step() {
        if (this._nativeReader.step()) {
            this._changeIndex++;
            return true;
        }
        return false;
    }
    /** Check if reader current on a row
     * @beta
    */
    get hasRow() {
        return this._nativeReader.hasRow();
    }
    /** Check if its current change is indirect
     * @beta
    */
    get isIndirect() {
        return this._nativeReader.isIndirectChange();
    }
    /** Get count of columns in current change
     * @beta
    */
    get columnCount() {
        return this._nativeReader.getColumnCount();
    }
    /** Get operation that caused the change
     * @beta
    */
    get op() {
        if (this._nativeReader.getOpCode() === DbOpcode.Insert)
            return "Inserted";
        if (this._nativeReader.getOpCode() === DbOpcode.Delete)
            return "Deleted";
        return "Updated";
    }
    /** Get primary key value array
     * @beta
    */
    get primaryKeyValues() {
        return this._nativeReader.getPrimaryKeys();
    }
    /** Get primary key columns.
     * @note To this to work db arg must be set when opening changeset file.
     * @beta
     */
    getPrimaryKeyColumnNames() {
        const cols = this.getColumnNames(this.tableName);
        if (!this._disableSchemaCheck && cols.length !== this.columnCount)
            throw new Error(`changeset table ${this.tableName} columns count does not match db declared table. ${this.columnCount} <> ${cols.length}`);
        return this._nativeReader.getPrimaryKeyColumnIndexes().map((i) => cols[i]);
    }
    /** Get current change table.
     * @beta
    */
    get tableName() {
        return this._nativeReader.getTableName();
    }
    /**
     * Get changed binary value for a column
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns value for changed column
     * @beta
     */
    getChangeValueType(columnIndex, stage) {
        return this._nativeReader.getColumnValueType(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get changed binary value for a column
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns value for changed column
     * @beta
     */
    getChangeValueBinary(columnIndex, stage) {
        return this._nativeReader.getColumnValueBinary(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get changed double value for a column
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns value for changed column
     * @beta
     */
    getChangeValueDouble(columnIndex, stage) {
        return this._nativeReader.getColumnValueDouble(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get changed Id value for a column
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns value for changed column
     * @beta
     */
    getChangeValueId(columnIndex, stage) {
        return this._nativeReader.getColumnValueId(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get changed integer value for a column
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns value for changed column
     * @beta
     */
    getChangeValueInteger(columnIndex, stage) {
        return this._nativeReader.getColumnValueInteger(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get changed text value for a column
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns value for changed column
     * @beta
     */
    getChangeValueText(columnIndex, stage) {
        return this._nativeReader.getColumnValueText(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Check if change value is null
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns true if value is null
     * @beta
     */
    isColumnValueNull(columnIndex, stage) {
        return this._nativeReader.isColumnValueNull(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get change value type
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns change value type
     * @beta
     */
    getColumnValueType(columnIndex, stage) {
        return this._nativeReader.getColumnValueType(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get changed value for a column
     * @param columnIndex index of column in current change
     * @param stage old or new value for change.
     * @returns value for changed column
     * @beta
     */
    getChangeValue(columnIndex, stage) {
        return this._nativeReader.getColumnValue(columnIndex, stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get all changed value in current change as array
     * @param stage old or new values for current change.
     * @returns array of values.
     * @beta
     */
    getChangeValuesArray(stage) {
        return this._nativeReader.getRow(stage === "New" ? DbChangeStage.New : DbChangeStage.Old);
    }
    /**
     * Get change as object and format its content.
     * @param stage old or new value for current change.
     * @param args change format options
     * @returns return object or undefined
     * @beta
     */
    getChangeValuesObject(stage, args = {}) {
        const cols = this.getColumnNames(this.tableName);
        const row = this.getChangeValuesArray(stage);
        if (!row)
            return undefined;
        process.env;
        const minLen = Math.min(cols.length, row.length);
        if (!this._disableSchemaCheck && cols.length !== this.columnCount)
            throw new Error(`changeset table ${this.tableName} columns count does not match db declared table. ${this.columnCount} <> ${cols.length}`);
        const out = {};
        if (args.includeTableName) {
            out.$table = this.tableName;
        }
        if (args.includeOpCode) {
            out.$op = this.op;
        }
        if (args.includeStage) {
            out.$stage = stage;
        }
        if (args.includePrimaryKeyInUpdateNew && this.op === "Updated" && stage === "New") {
            const pkNames = this.getPrimaryKeyColumnNames();
            const pkValues = this.primaryKeyValues;
            pkNames.forEach((v, i) => {
                out[v] = pkValues[i];
            });
        }
        const isNullOrUndefined = (val) => typeof val === "undefined";
        for (let i = 0; i < minLen; ++i) {
            const columnValue = row[i];
            const columnName = cols[i];
            if (!args.includeNullColumns && isNullOrUndefined(columnValue))
                continue;
            out[columnName] = columnValue;
        }
        return out;
    }
    /**
     * Get list of column for a table. This function also caches the result.
     * @note To this to work db arg must be set when opening changeset file.
     * @param tableName name of the table for which columns are requested.
     * @returns columns of table.
     * @beta
     */
    getColumnNames(tableName) {
        const columns = this._schemaCache.get(tableName);
        if (columns)
            return columns;
        return this.db.withPreparedSqliteStatement("SELECT [name] FROM PRAGMA_TABLE_INFO(?) ORDER BY [cid]", (stmt) => {
            stmt.bindString(1, tableName);
            const tblCols = [];
            while (stmt.step() === DbResult.BE_SQLITE_ROW) {
                tblCols.push(stmt.getValueString(0));
            }
            this._schemaCache.set(tableName, tblCols);
            return tblCols;
        });
    }
    /** index of current change
     * @beta
     */
    get changeIndex() { return this._changeIndex; }
    /**
     * Close changeset
     * @beta
     */
    close() {
        this._changeIndex = 0;
        this._nativeReader.close();
    }
    /**
     * Dispose this object
     * @beta
     */
    [Symbol.dispose]() {
        this.close();
    }
    /**
     * Get Data Definition Language changes
     * @beta
     */
    getDdlChanges() {
        return this._nativeReader.getDdlChanges();
    }
}
//# sourceMappingURL=SqliteChangesetReader.js.map