"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Statement = void 0;
var _types = require("./types");
/**
 * Statement
 *
 * High-level wrapper around NativeStatement providing a clean API.
 * Handles parameter binding, row conversion, and result collection.
 */

/**
 * Prepared SQL statement
 */
class Statement {
  _finalized = false;
  constructor(statement, connection, execLock, extraIo) {
    this._statement = statement;
    this._connection = connection;
    this._execLock = execLock;
    this._extraIo = extraIo;
  }

  /**
   * Bind parameters to the statement
   *
   * @param params - Parameters to bind (array, object, or single value)
   * @returns this for chaining
   */
  bind(...params) {
    if (this._finalized) {
      throw new Error('Statement has been finalized');
    }

    // Flatten parameters if single array passed
    let flatParams;
    if (params.length === 1 && Array.isArray(params[0])) {
      flatParams = params[0];
    } else if (params.length === 1 && typeof params[0] === 'object' && params[0] !== null) {
      // Named parameters
      const namedParams = params[0];
      this.bindNamed(namedParams);
      return this;
    } else {
      flatParams = params;
    }

    // Bind positional parameters
    this.bindPositional(flatParams);
    return this;
  }

  /**
   * Bind positional parameters (1-indexed)
   *
   * @param params - Array of values to bind
   */
  bindPositional(params) {
    for (let i = 0; i < params.length; i++) {
      const position = i + 1; // 1-indexed
      const value = params[i];
      this.bindValue(position, value);
    }
  }

  /**
   * Bind named parameters
   *
   * @param params - Object with named parameters
   */
  bindNamed(params) {
    for (const [name, value] of Object.entries(params)) {
      // Get position for named parameter
      const position = this._statement.namedPosition(name);
      if (position < 0) {
        throw new Error(`Unknown parameter name: ${name}`);
      }
      this.bindValue(position, value);
    }
  }

  /**
   * Bind a single value at a position
   *
   * @param position - 1-indexed position
   * @param value - Value to bind
   */
  bindValue(position, value) {
    if (value === null || value === undefined) {
      this._statement.bindPositionalNull(position);
    } else if (typeof value === 'number') {
      // Check if integer or float
      if (Number.isInteger(value)) {
        this._statement.bindPositionalInt(position, value);
      } else {
        this._statement.bindPositionalDouble(position, value);
      }
    } else if (typeof value === 'string') {
      this._statement.bindPositionalText(position, value);
    } else if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
      const buffer = value;
      this._statement.bindPositionalBlob(position, buffer);
    } else {
      throw new Error(`Unsupported parameter type: ${typeof value}`);
    }
  }

  /**
   * Execute statement without returning rows (for INSERT, UPDATE, DELETE)
   *
   * @param params - Optional parameters to bind
   * @returns Result with changes and lastInsertRowid
   */
  async run(...params) {
    if (this._finalized) {
      throw new Error('Statement has been finalized');
    }
    if (this._execLock) {
      await this._execLock.acquire();
    }
    try {
      // Bind parameters inside the lock to prevent concurrent bind/execute races
      if (params.length > 0) {
        this.bind(...params);
      }
      return await this._runInner();
    } finally {
      this._statement.reset();
      if (this._execLock) {
        this._execLock.release();
      }
    }
  }

  /**
   * Execute without acquiring the lock (caller already holds it).
   * Used by Database.exec() and Transaction which acquire the lock once.
   */
  async rawRun() {
    if (this._finalized) {
      throw new Error('Statement has been finalized');
    }
    try {
      return await this._runInner();
    } finally {
      this._statement.reset();
    }
  }
  async _runInner() {
    // Execute statement with IO handling
    const result = await this.executeWithIo();
    return {
      changes: result.rowsChanged,
      lastInsertRowid: this._connection ? this._connection.lastInsertRowid() : 0
    };
  }

  /**
   * Execute statement handling potential IO (for partial sync)
   * Matches Python's _run_execute_with_io pattern
   *
   * @returns Execution result
   */
  async executeWithIo() {
    while (true) {
      const result = this._statement.execute();
      if (result.status === _types.TursoStatus.IO) {
        // Statement needs IO (e.g., loading missing pages with partial sync)
        this._statement.runIo();

        // Drain sync engine IO queue
        if (this._extraIo) {
          await this._extraIo();
        }
        continue;
      }
      if (result.status !== _types.TursoStatus.DONE) {
        throw new Error(`Statement execution failed with status: ${result.status}`);
      }
      return result;
    }
  }

  /**
   * Step statement once handling potential IO (for partial sync)
   * Matches Python's _step_once_with_io pattern
   *
   * @returns Status code
   */
  async stepWithIo() {
    while (true) {
      const status = this._statement.step();
      if (status === _types.TursoStatus.IO) {
        // Statement needs IO (e.g., loading missing pages with partial sync)
        this._statement.runIo();

        // Drain sync engine IO queue
        if (this._extraIo) {
          await this._extraIo();
        }
        continue;
      }
      return status;
    }
  }

  /**
   * Execute statement and return first row
   *
   * @param params - Optional parameters to bind
   * @returns First row or undefined
   */
  async get(...params) {
    if (this._finalized) {
      throw new Error('Statement has been finalized');
    }
    if (this._execLock) {
      await this._execLock.acquire();
    }
    try {
      // Bind parameters inside the lock to prevent concurrent bind/execute races
      if (params.length > 0) {
        this.bind(...params);
      }

      // Step once with async IO handling
      const status = await this.stepWithIo();
      if (status === _types.TursoStatus.ROW) {
        const row = this.readRow();
        return row;
      }
      if (status === _types.TursoStatus.DONE) {
        return undefined;
      }
      throw new Error(`Statement step failed with status: ${status}`);
    } finally {
      this._statement.reset();
      if (this._execLock) {
        this._execLock.release();
      }
    }
  }

  /**
   * Execute statement and return all rows
   *
   * @param params - Optional parameters to bind
   * @returns Array of rows
   */
  async all(...params) {
    if (this._finalized) {
      throw new Error('Statement has been finalized');
    }
    if (this._execLock) {
      await this._execLock.acquire();
    }
    try {
      // Bind parameters inside the lock to prevent concurrent bind/execute races
      if (params.length > 0) {
        this.bind(...params);
      }
      const rows = [];

      // Step through all rows with async IO handling
      while (true) {
        const status = await this.stepWithIo();
        if (status === _types.TursoStatus.ROW) {
          rows.push(this.readRow());
        } else if (status === _types.TursoStatus.DONE) {
          break;
        } else {
          throw new Error(`Statement step failed with status: ${status}`);
        }
      }
      return rows;
    } finally {
      this._statement.reset();
      if (this._execLock) {
        this._execLock.release();
      }
    }
  }

  /**
   * Read current row into an object
   *
   * @returns Row object with column name keys
   */
  readRow() {
    const row = {};
    const columnCount = this._statement.columnCount();
    for (let i = 0; i < columnCount; i++) {
      const name = this._statement.columnName(i);
      if (!name) {
        throw new Error(`Failed to get column name at index ${i}`);
      }
      const value = this.readColumnValue(i);
      row[name] = value;
    }
    return row;
  }

  /**
   * Read value at column index
   *
   * @param index - Column index
   * @returns Column value
   */
  readColumnValue(index) {
    const kind = this._statement.rowValueKind(index);
    switch (kind) {
      case _types.TursoType.NULL:
        return null;
      case _types.TursoType.INTEGER:
        return this._statement.rowValueInt(index);
      case _types.TursoType.REAL:
        return this._statement.rowValueDouble(index);
      case _types.TursoType.TEXT:
        // Use rowValueText which directly returns a string from C++ (avoids encoding issues)
        return this._statement.rowValueText(index);
      case _types.TursoType.BLOB:
        return this._statement.rowValueBytesPtr(index) || new ArrayBuffer(0);
      default:
        throw new Error(`Unknown column type: ${kind}`);
    }
  }

  /**
   * Reset statement for re-execution
   *
   * @returns this for chaining
   */
  reset() {
    if (this._finalized) {
      throw new Error('Statement has been finalized');
    }
    this._statement.reset();
    return this;
  }

  /**
   * Finalize and release statement resources
   */
  async finalize() {
    if (this._finalized) {
      return;
    }
    if (this._execLock) {
      await this._execLock.acquire();
    }
    try {
      while (true) {
        const status = this._statement.finalize();
        if (status === _types.TursoStatus.IO) {
          // Statement needs IO (e.g., loading missing pages with partial sync)
          this._statement.runIo();

          // Drain sync engine IO queue
          if (this._extraIo) {
            await this._extraIo();
          }
          continue;
        }
        if (status !== _types.TursoStatus.DONE) {
          throw new Error(`Statement finalization failed with status: ${status}`);
        }
        break;
      }
      this._finalized = true;
    } finally {
      if (this._execLock) {
        this._execLock.release();
      }
    }
  }

  /**
   * Check if statement has been finalized
   */
  get finalized() {
    return this._finalized;
  }
}
exports.Statement = Statement;
//# sourceMappingURL=Statement.js.map