import { NativeModules } from 'react-native';
if (global.__OPSQLiteProxy == null) {
  if (NativeModules.OPSQLite == null) {
    throw new Error('Base module not found. Maybe try rebuilding the app.');
  }
  if (NativeModules.OPSQLite.install == null) {
    throw new Error('Failed to install op-sqlite: React Native is not running on-device. OPSQLite can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.');
  }

  // Call the synchronous blocking install() function
  const result = NativeModules.OPSQLite.install();
  if (result !== true) {
    throw new Error(`Failed to install op-sqlite: The native OPSQLite Module could not be installed! Looks like something went wrong when installing JSI bindings, check the native logs for more info`);
  }

  // Check again if the constructor now exists. If not, throw an error.
  if (global.__OPSQLiteProxy == null) {
    throw new Error('Failed to install op-sqlite, the native initializer function does not exist. Are you trying to use OPSQLite from different JS Runtimes?');
  }
}
const proxy = global.__OPSQLiteProxy;
export const OPSQLite = proxy;
export const {
  IOS_DOCUMENT_PATH,
  IOS_LIBRARY_PATH,
  ANDROID_DATABASE_PATH,
  ANDROID_FILES_PATH,
  ANDROID_EXTERNAL_FILES_PATH
} = !!NativeModules.OPSQLite.getConstants ? NativeModules.OPSQLite.getConstants() : NativeModules.OPSQLite;

/**
 * Object returned by SQL Query executions {
 *  insertId: Represent the auto-generated row id if applicable
 *  rowsAffected: Number of affected rows if result of a update query
 *  message: if status === 1, here you will find error description
 *  rows: if status is undefined or 0 this object will contain the query results
 * }
 *
 * @interface QueryResult
 */

/**
 * Column metadata
 * Describes some information about columns fetched by the query
 */

/**
 * Allows the execution of bulk of sql commands
 * inside a transaction
 * If a single query must be executed many times with different arguments, its preferred
 * to declare it a single time, and use an array of array parameters.
 */

/**
 * status: 0 or undefined for correct execution, 1 for error
 * message: if status === 1, here you will find error description
 * rowsAffected: Number of affected rows if status == 0
 */

/**
 * Result of loading a file and executing every line as a SQL command
 * Similar to BatchQueryResult
 */

const locks = {};
function enhanceDB(db, options) {
  const lock = {
    queue: [],
    inProgress: false
  };
  const startNextTransaction = () => {
    if (lock.inProgress) {
      // Transaction is already in process bail out
      return;
    }
    if (lock.queue.length) {
      lock.inProgress = true;
      const tx = lock.queue.shift();
      if (!tx) {
        throw new Error('Could not get a operation on database');
      }
      setImmediate(() => {
        tx.start();
      });
    }
  };

  // spreading the object is not working, so we need to do it manually
  let enhancedDb = {
    delete: db.delete,
    attach: db.attach,
    detach: db.detach,
    executeBatch: db.executeBatch,
    loadFile: db.loadFile,
    updateHook: db.updateHook,
    commitHook: db.commitHook,
    rollbackHook: db.rollbackHook,
    loadExtension: db.loadExtension,
    executeRaw: db.executeRaw,
    getDbPath: db.getDbPath,
    reactiveExecute: db.reactiveExecute,
    sync: db.sync,
    flushPendingReactiveQueries: db.flushPendingReactiveQueries,
    close: () => {
      db.close();
      delete locks[options.url];
    },
    executeWithHostObjects: async (query, params) => {
      const sanitizedParams = params?.map(p => {
        if (ArrayBuffer.isView(p)) {
          return p.buffer;
        }
        return p;
      });
      const result = await db.executeWithHostObjects(query, sanitizedParams);
      return result;
    },
    executeSync: (query, params) => {
      const sanitizedParams = params?.map(p => {
        if (ArrayBuffer.isView(p)) {
          return p.buffer;
        }
        return p;
      });
      let intermediateResult = db.executeSync(query, sanitizedParams);
      let rows = [];
      for (let i = 0; i < (intermediateResult.rawRows?.length ?? 0); i++) {
        let row = {};
        let rawRow = intermediateResult.rawRows[i];
        for (let j = 0; j < intermediateResult.columnNames.length; j++) {
          let columnName = intermediateResult.columnNames[j];
          let value = rawRow[j];
          row[columnName] = value;
        }
        rows.push(row);
      }
      let res = {
        ...intermediateResult,
        rows
      };
      delete res.rawRows;
      return res;
    },
    execute: async (query, params) => {
      const sanitizedParams = params?.map(p => {
        if (ArrayBuffer.isView(p)) {
          return p.buffer;
        }
        return p;
      });
      let intermediateResult = await db.execute(query, sanitizedParams);
      let rows = [];
      for (let i = 0; i < (intermediateResult.rawRows?.length ?? 0); i++) {
        let row = {};
        let rawRow = intermediateResult.rawRows[i];
        for (let j = 0; j < intermediateResult.columnNames.length; j++) {
          let columnName = intermediateResult.columnNames[j];
          let value = rawRow[j];
          row[columnName] = value;
        }
        rows.push(row);
      }
      let res = {
        ...intermediateResult,
        rows
      };
      delete res.rawRows;
      return res;
    },
    prepareStatement: query => {
      const stmt = db.prepareStatement(query);
      return {
        bind: params => {
          const sanitizedParams = params.map(p => {
            if (ArrayBuffer.isView(p)) {
              return p.buffer;
            }
            return p;
          });
          stmt.bind(sanitizedParams);
        },
        execute: stmt.execute
      };
    },
    transaction: async fn => {
      let isFinalized = false;
      const execute = async (query, params) => {
        if (isFinalized) {
          throw Error(`OP-Sqlite Error: Database: ${options.url}. Cannot execute query on finalized transaction`);
        }
        return await enhancedDb.execute(query, params);
      };
      const commit = async () => {
        if (isFinalized) {
          throw Error(`OP-Sqlite Error: Database: ${options.url}. Cannot execute query on finalized transaction`);
        }
        const result = await enhancedDb.execute('COMMIT;');
        await enhancedDb.flushPendingReactiveQueries();
        isFinalized = true;
        return result;
      };
      const rollback = async () => {
        if (isFinalized) {
          throw Error(`OP-Sqlite Error: Database: ${options.url}. Cannot execute query on finalized transaction`);
        }
        const result = await enhancedDb.execute('ROLLBACK;');
        isFinalized = true;
        return result;
      };
      async function run() {
        try {
          await enhancedDb.execute('BEGIN TRANSACTION;');
          await fn({
            commit,
            execute,
            rollback
          });
          if (!isFinalized) {
            await commit();
          }
        } catch (executionError) {
          // console.warn('transaction error', executionError);
          if (!isFinalized) {
            try {
              await rollback();
            } catch (rollbackError) {
              throw rollbackError;
            }
          }
          throw executionError;
        } finally {
          lock.inProgress = false;
          isFinalized = false;
          startNextTransaction();
        }
      }
      return await new Promise((resolve, reject) => {
        const tx = {
          start: () => {
            run().then(resolve).catch(reject);
          }
        };
        lock.queue.push(tx);
        startNextTransaction();
      });
    }
  };
  return enhancedDb;
}

/** Open a replicating connection via libsql to a turso db
 * libsql needs to be enabled on your package.json
 */
export const openSync = options => {
  if (!isLibsql()) {
    throw new Error('This function is only available for libsql');
  }
  const db = OPSQLite.openSync(options);
  const enhancedDb = enhanceDB(db, options);
  return enhancedDb;
};

/** Open a remote connection via libsql to a turso db
 * libsql needs to be enabled on your package.json
 */
export const openRemote = options => {
  if (!isLibsql()) {
    throw new Error('This function is only available for libsql');
  }
  const db = OPSQLite.openRemote(options);
  const enhancedDb = enhanceDB(db, options);
  return enhancedDb;
};
export const open = options => {
  const db = OPSQLite.open(options);
  const enhancedDb = enhanceDB(db, options);
  return enhancedDb;
};
export const moveAssetsDatabase = async args => {
  return NativeModules.OPSQLite.moveAssetsDatabase(args);
};
export const getDylibPath = (bundle, name) => {
  return NativeModules.OPSQLite.getDylibPath(bundle, name);
};
export const isSQLCipher = () => {
  return OPSQLite.isSQLCipher();
};
export const isLibsql = () => {
  return OPSQLite.isLibsql();
};
//# sourceMappingURL=index.js.map