//  SPDX-License-Identifier: LGPL-2.1-or-later
//  Copyright (c) 2015-2025 MariaDB Corporation Ab

'use strict';

import { EventEmitter } from 'node:events';
import Pool from './pool.js';
import ConnectionPromise, { paramSetter } from './connection-promise.js';
import * as Errors from './misc/errors.js';

const hasAsyncDispose = typeof Symbol.asyncDispose === 'symbol';

class PoolPromise extends EventEmitter {
  #pool;
  constructor(options) {
    super();
    this.#pool = new Pool(options);
    const poolPromise = this;
    this.#pool._onNewConnection = (conn) => {
      try {
        poolPromise.emit('connection', new ConnectionPromise(conn));
      } catch (err) {
        // ignore user errors in connection event handler
      }
    };
    this.#pool.on('acquire', this.emit.bind(this, 'acquire'));
    this.#pool.on('enqueue', this.emit.bind(this, 'enqueue'));
    this.#pool.on('release', this.emit.bind(this, 'release'));
    this.#pool.on('error', this.emit.bind(this, 'error'));
  }

  get closed() {
    return this.#pool.closed;
  }

  /**
   * Get the current total connection number.
   * @return {number}
   */
  totalConnections() {
    return this.#pool.totalConnections();
  }

  /**
   * Get current active connections.
   * @return {number}
   */
  activeConnections() {
    return this.#pool.activeConnections();
  }

  /**
   * Get the current idle connection number.
   * @return {number}
   */
  idleConnections() {
    return this.#pool.idleConnections();
  }

  /**
   * Get current stacked connection request.
   * @return {number}
   */
  taskQueueSize() {
    return this.#pool.taskQueueSize();
  }

  escape(value) {
    return this.#pool.escape(value);
  }

  escapeId(value) {
    return this.#pool.escapeId(value);
  }

  /**
   * End pool
   *
   * @return Promise
   **/
  end() {
    return this.#pool.end();
  }

  /**
   * Retrieve a connection from the pool.
   * Create a new one if the limit is not reached.
   * wait until acquireTimeout.
   *
   */
  async getConnection() {
    const cmdParam = {};
    if (this.#pool.opts.connOptions.trace) Error.captureStackTrace(cmdParam);
    return new Promise((resolve, reject) => {
      this.#pool.getConnection(cmdParam, (err, baseConn) => {
        if (err) {
          reject(err);
        } else {
          const conn = new ConnectionPromise(baseConn);
          conn.release = () => new Promise(baseConn.release);
          conn.end = conn.release;
          conn.close = conn.release;
          if (hasAsyncDispose) {
            conn[Symbol.asyncDispose] = () => conn.release();
          }
          resolve(conn);
        }
      });
    });
  }

  /**
   * Execute a query using text protocol with callback emit columns/data/end/error
   * events to permit streaming big result-set
   *
   * @param sql     sql parameter Object can be used to supersede the default option.
   *                Object must then have a `sql` property.
   * @param values  object / array of placeholder values (not mandatory)
   */
  query(sql, values) {
    const cmdParam = paramSetter(sql, values);
    if (this.#pool.opts.connOptions.trace) Error.captureStackTrace(cmdParam);
    return new Promise((resolve, reject) => {
      return this.#pool.getConnection(cmdParam, (err, baseConn) => {
        if (err) {
          reject(err);
        } else {
          baseConn.query(
            cmdParam,
            (res) => {
              this.#pool.release(baseConn);
              resolve(res);
            },
            (err) => {
              this.#pool.release(baseConn);
              reject(err);
            }
          );
        }
      });
    });
  }

  /**
   * Execute a query using binary protocol with callback emit columns/data/end/error
   * events to permit streaming big result-set
   *
   * @param sql     sql parameter Object can be used to supersede the default option.
   *                Object must then have a `sql` property.
   * @param values  object / array of placeholder values (not mandatory)
   */
  execute(sql, values) {
    const cmdParam = paramSetter(sql, values);
    if (this.#pool.opts.connOptions.trace) Error.captureStackTrace(cmdParam);
    return new Promise((resolve, reject) => {
      return this.#pool.getConnection(cmdParam, (err, baseConn) => {
        if (err) {
          reject(err);
        } else {
          baseConn.prepareExecute(
            cmdParam,
            (res) => {
              this.#pool.release(baseConn);
              resolve(res);
            },
            (err) => {
              this.#pool.release(baseConn);
              reject(err);
            }
          );
        }
      });
    });
  }

  /**
   * execute a batch
   *
   * @param sql     sql parameter Object can be used to supersede the default option.
   *                Object must then have a `sql` property.
   * @param values  array of placeholder values
   */
  batch(sql, values) {
    const cmdParam = paramSetter(sql, values);
    if (this.#pool.opts.connOptions.trace) Error.captureStackTrace(cmdParam);
    return new Promise((resolve, reject) => {
      return this.#pool.getConnection(cmdParam, (err, baseConn) => {
        if (err) {
          reject(err);
        } else {
          baseConn.batch(
            cmdParam,
            (res) => {
              this.#pool.release(baseConn);
              resolve(res);
            },
            (err) => {
              this.#pool.release(baseConn);
              reject(err);
            }
          );
        }
      });
    });
  }

  /**
   * Import SQL file.
   *
   * @param opts JSON array with 2 possible fields: file and database
   */
  importFile(opts) {
    if (!opts) {
      return Promise.reject(
        Errors.createError(
          'SQL file parameter is mandatory',
          Errors.client.ER_MISSING_SQL_PARAMETER,
          null,
          'HY000',
          null,
          false,
          null
        )
      );
    }

    return new Promise((resolve, reject) => {
      return this.#pool.getConnection({}, (err, baseConn) => {
        if (err) {
          reject(err);
        } else {
          baseConn.importFile(
            { file: opts.file, database: opts.database },
            (res) => {
              this.#pool.release(baseConn);
              resolve(res);
            },
            (err) => {
              this.#pool.release(baseConn);
              reject(err);
            }
          );
        }
      });
    });
  }

  toString() {
    return 'poolPromise(' + this.#pool.toString() + ')';
  }
}

if (hasAsyncDispose) {
  PoolPromise.prototype[Symbol.asyncDispose] = function () {
    return this.end();
  };
}

export default PoolPromise;
