import { NativeModules, Platform } from 'react-native';
export { Storage } from './Storage';

/**
 * 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
 */

if (global.__OPSQLiteProxy == null) {
  if (NativeModules.OPSQLite == null) {
    throw new Error('Base module not found. Did you do a pod install/clear the gradle cache?');
  }

  // Call the synchronous blocking install() function
  const installed = NativeModules.OPSQLite.install();
  if (!installed) {
    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('OPSqlite native object is not available. Something is wrong. Check the native logs for more information.');
  }
}
const proxy = global.__OPSQLiteProxy;
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;
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 does not work with HostObjects (db)
  // We need to manually assign the fields
  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,
    close: db.close,
    executeWithHostObjects: async (query, params) => {
      const sanitizedParams = params?.map(p => {
        if (ArrayBuffer.isView(p)) {
          return p.buffer;
        }
        return p;
      });
      return sanitizedParams ? await db.executeWithHostObjects(query, sanitizedParams) : await db.executeWithHostObjects(query);
    },
    executeSync: (query, params) => {
      const sanitizedParams = params?.map(p => {
        if (ArrayBuffer.isView(p)) {
          return p.buffer;
        }
        return p;
      });
      let intermediateResult = sanitizedParams ? db.executeSync(query, sanitizedParams) : db.executeSync(query);
      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: async params => {
          const sanitizedParams = params.map(p => {
            if (ArrayBuffer.isView(p)) {
              return p.buffer;
            }
            return p;
          });
          await 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.name || 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.name || options.url}. Cannot execute query on finalized transaction`);
        }
        const result = await enhancedDb.execute('COMMIT;');
        await db.flushPendingReactiveQueries();
        isFinalized = true;
        return result;
      };
      const rollback = async () => {
        if (isFinalized) {
          throw Error(`OP-Sqlite Error: Database: ${options.name || 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) {
          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 = params => {
  if (!isLibsql()) {
    throw new Error('This function is only available for libsql');
  }
  const db = OPSQLite.openSync(params);
  const enhancedDb = enhanceDB(db, params);
  return enhancedDb;
};

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

/**
 * Open a connection to a local sqlite or sqlcipher database
 * If you want libsql remote or sync connections, use openSync or openRemote
 */
export const open = params => {
  if (params.location?.startsWith('file://')) {
    console.warn("[op-sqlite] You are passing a path with 'file://' prefix, it's automatically removed");
    params.location = params.location.substring(7);
  }
  const db = OPSQLite.open(params);
  const enhancedDb = enhanceDB(db, params);
  return enhancedDb;
};

/**
 * Moves the database from the assets folder to the default path (check the docs) or to a custom path
 * It DOES NOT OVERWRITE the database if it already exists in the destination path
 * if you want to overwrite the database, you need to pass the overwrite flag as true
 * @param args object with the parameters for the operaiton
 * @returns promise, rejects if failed to move the database, resolves if the operation was successful
 */
export const moveAssetsDatabase = async args => {
  return NativeModules.OPSQLite.moveAssetsDatabase(args);
};

/**
 * Used to load a dylib file that contains a sqlite 3 extension/plugin
 * It returns the raw path to the actual file which then needs to be passed to the loadExtension function
 * Check the docs for more information
 * @param bundle the iOS bundle identifier of the .framework
 * @param name the file name of the dylib file
 * @returns
 */
export const getDylibPath = (bundle, name) => {
  return NativeModules.OPSQLite.getDylibPath(bundle, name);
};
export const isSQLCipher = () => {
  return OPSQLite.isSQLCipher();
};
export const isLibsql = () => {
  return OPSQLite.isLibsql();
};
export const isIOSEmbeeded = () => {
  if (Platform.OS !== 'ios') {
    return false;
  }
  return OPSQLite.isIOSEmbedded();
};
//# sourceMappingURL=index.js.map