/**
 * @since 4.0.0
 */
import { Clock } from "../../Clock.js";
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import * as Exit from "../../Exit.js";
import * as Option from "../../Option.js";
import * as Scope from "../../Scope.js";
import * as Stream from "../../Stream.js";
import * as Tracer from "../../Tracer.js";
import { Reactivity } from "../reactivity/Reactivity.js";
import * as Statement from "./Statement.js";
const TypeId = "~effect/sql/SqlClient";
/**
 * @category models
 * @since 4.0.0
 */
export const SqlClient = /*#__PURE__*/Context.Service("effect/sql/SqlClient");
let clientIdCounter = 0;
/**
 * @category constructors
 * @since 4.0.0
 */
export const make = /*#__PURE__*/Effect.fnUntraced(function* (options) {
  const transactionService = options.transactionService ?? TransactionConnection(clientIdCounter++);
  const getConnection = Effect.flatMap(Effect.serviceOption(transactionService), Option.match({
    onNone: () => options.acquirer,
    onSome: ([conn]) => Effect.succeed(conn)
  }));
  const beginTransaction = options.beginTransaction ?? "BEGIN";
  const commit = options.commit ?? "COMMIT";
  const savepoint = options.savepoint ?? (name => `SAVEPOINT ${name}`);
  const rollback = options.rollback ?? "ROLLBACK";
  const rollbackSavepoint = options.rollbackSavepoint ?? (name => `ROLLBACK TO SAVEPOINT ${name}`);
  const transactionAcquirer = options.transactionAcquirer ?? options.acquirer;
  const withTransaction = makeWithTransaction({
    transactionService,
    spanAttributes: options.spanAttributes,
    acquireConnection: Effect.flatMap(Scope.make(), scope => Effect.map(Scope.provide(transactionAcquirer, scope), conn => [scope, conn])),
    begin: conn => conn.executeUnprepared(beginTransaction, [], undefined),
    savepoint: (conn, id) => conn.executeUnprepared(savepoint(`effect_sql_${id}`), [], undefined),
    commit: conn => conn.executeUnprepared(commit, [], undefined),
    rollback: conn => conn.executeUnprepared(rollback, [], undefined),
    rollbackSavepoint: (conn, id) => conn.executeUnprepared(rollbackSavepoint(`effect_sql_${id}`), [], undefined)
  });
  const reactivity = yield* Reactivity;
  const client = Object.assign(Statement.make(getConnection, options.compiler, options.spanAttributes, options.transformRows), {
    [TypeId]: TypeId,
    safe: undefined,
    withTransaction,
    transactionService,
    reserve: transactionAcquirer,
    withoutTransforms() {
      if (options.transformRows === undefined) {
        return this;
      }
      const statement = Statement.make(getConnection, options.compiler.withoutTransform, options.spanAttributes, undefined);
      const client = Object.assign(statement, {
        ...this,
        ...statement
      });
      client.safe = client;
      client.withoutTransforms = () => client;
      return client;
    },
    reactive: options.reactiveQueue ? (keys, effect) => options.reactiveQueue(keys, effect).pipe(Effect.map(Stream.fromQueue), Stream.unwrap) : reactivity.stream,
    reactiveMailbox: options.reactiveQueue ?? reactivity.query
  });
  client.safe = client;
  return client;
});
/**
 * @since 4.0.0
 * @category transactions
 */
export const makeWithTransaction = options => effect => {
  return Effect.uninterruptibleMask(restore => Effect.useSpan("sql.transaction", {
    kind: "client"
  }, span => Effect.withFiber(fiber => {
    for (const [key, value] of options.spanAttributes) {
      span.attribute(key, value);
    }
    const services = fiber.context;
    const clock = fiber.getRef(Clock);
    const connOption = Context.getOption(services, options.transactionService);
    const conn = connOption._tag === "Some" ? Effect.succeed([undefined, connOption.value[0]]) : options.acquireConnection;
    const id = connOption._tag === "Some" ? connOption.value[1] + 1 : 0;
    return Effect.flatMap(conn, ([scope, conn]) => (id === 0 ? options.begin(conn) : options.savepoint(conn, id)).pipe(Effect.flatMap(() => Effect.provideContext(restore(effect), Context.mutate(services, services => services.pipe(Context.add(options.transactionService, [conn, id]), Context.add(Tracer.ParentSpan, span))))), Effect.exit, Effect.flatMap(exit => {
      let effect;
      if (Exit.isSuccess(exit)) {
        if (id === 0) {
          span.event("db.transaction.commit", clock.currentTimeNanosUnsafe());
          effect = Effect.orDie(options.commit(conn));
        } else {
          span.event("db.transaction.savepoint", clock.currentTimeNanosUnsafe());
          effect = Effect.void;
        }
      } else {
        span.event("db.transaction.rollback", clock.currentTimeNanosUnsafe());
        effect = Effect.orDie(id > 0 ? options.rollbackSavepoint(conn, id) : options.rollback(conn));
      }
      const withScope = scope !== undefined ? Effect.ensuring(effect, Scope.close(scope, exit)) : effect;
      return Effect.flatMap(withScope, () => exit);
    })));
  })));
};
/**
 * @since 4.0.0
 */
export const TransactionConnection = clientId => Context.Service(`effect/sql/SqlClient/TransactionConnection/${clientId}`);
/**
 * @since 4.0.0
 */
export const SafeIntegers = /*#__PURE__*/Context.Reference("effect/sql/SqlClient/SafeIntegers", {
  defaultValue: () => false
});
//# sourceMappingURL=SqlClient.js.map