/**
 * @since 4.0.0
 */
import * as Arr from "../../Array.js";
import * as Cause from "../../Cause.js";
import * as Effect from "../../Effect.js";
import * as Equal from "../../Equal.js";
import * as Exit from "../../Exit.js";
import * as Hash from "../../Hash.js";
import * as MutableHashMap from "../../MutableHashMap.js";
import * as Request from "../../Request.js";
import * as RequestResolver from "../../RequestResolver.js";
import * as Schema from "../../Schema.js";
import * as SqlClient from "./SqlClient.js";
import { ResultLengthMismatch } from "./SqlError.js";
const SqlRequestProto = {
  ...Request.Class.prototype,
  [Equal.symbol](that) {
    return Equal.equals(this.payload, that.payload);
  },
  [Hash.symbol]() {
    return Hash.hash(this.payload);
  }
};
/**
 * @since 4.0.0
 * @category requests
 */
export const request = function () {
  if (arguments.length === 1) {
    const resolver = arguments[0];
    return payload => Effect.request(SqlRequest(payload), resolver);
  }
  return Effect.request(SqlRequest(arguments[0]), arguments[1]);
};
/**
 * @since 4.0.0
 * @category requests
 */
export const SqlRequest = payload => {
  const self = Object.create(SqlRequestProto);
  self.payload = payload;
  return self;
};
/**
 * Create a resolver for a sql query with a request schema and a result schema.
 *
 * The request schema is used to validate the input of the query.
 * The result schema is used to validate the output of the query.
 *
 * Results are mapped to the requests in order, so the length of the results must match the length of the requests.
 *
 * @since 4.0.0
 * @category resolvers
 */
export const ordered = options => {
  const decodeArray = Schema.decodeUnknownEffect(Schema.Array(options.Result));
  return RequestResolver.makeGrouped({
    key: transactionKey,
    resolver: Effect.fnUntraced(function* (entries) {
      const inputs = yield* partitionRequests(entries, options.Request);
      const results = yield* options.execute(inputs).pipe(Effect.provideContext(entries[0].context));
      if (results.length !== inputs.length) {
        return yield* new ResultLengthMismatch({
          expected: inputs.length,
          actual: results.length
        });
      }
      const decodedResults = yield* decodeArray(results).pipe(Effect.provideContext(entries[0].context));
      for (let i = 0; i < entries.length; i++) {
        entries[i].completeUnsafe(Exit.succeed(decodedResults[i]));
      }
    })
  });
};
/**
 * Create a resolver the can return multiple results for a single request.
 *
 * Results are grouped by a common key extracted from the request and result.
 *
 * @since 4.0.0
 * @category resolvers
 */
export const grouped = options => {
  const decodeResults = Schema.decodeUnknownEffect(Schema.Array(options.Result));
  return RequestResolver.makeGrouped({
    key: transactionKey,
    resolver: Effect.fnUntraced(function* (entries) {
      const inputs = yield* partitionRequests(entries, options.Request);
      const resultMap = MutableHashMap.empty();
      const results = yield* options.execute(inputs).pipe(Effect.provideContext(entries[0].context));
      const decodedResults = yield* decodeResults(results).pipe(Effect.provideContext(entries[0].context));
      for (let i = 0, len = decodedResults.length; i < len; i++) {
        const result = decodedResults[i];
        const key = options.ResultGroupKey(result, results[i]);
        const group = MutableHashMap.get(resultMap, key);
        if (group._tag === "None") {
          MutableHashMap.set(resultMap, key, [result]);
        } else {
          group.value.push(result);
        }
      }
      for (let i = 0, len = entries.length; i < len; i++) {
        const entry = entries[i];
        const key = options.RequestGroupKey(entry.request.payload);
        const result = MutableHashMap.get(resultMap, key);
        entry.completeUnsafe(result._tag === "None" ? constNoSuchElement : Exit.succeed(result.value));
      }
    })
  });
};
/**
 * Create a resolver that resolves results by id.
 *
 * @since 4.0.0
 * @category resolvers
 */
export const findById = options => {
  const decodeResults = Schema.decodeUnknownEffect(Schema.Array(options.Result));
  return RequestResolver.makeGrouped({
    key: transactionKey,
    resolver: Effect.fnUntraced(function* (entries) {
      const [inputs, idMap] = yield* partitionRequestsById(entries, options.Id);
      const results = yield* options.execute(inputs).pipe(Effect.provideContext(entries[0].context));
      const decodedResults = yield* decodeResults(results).pipe(Effect.provideContext(entries[0].context));
      for (let i = 0; i < decodedResults.length; i++) {
        const result = decodedResults[i];
        const id = options.ResultId(result, results[i]);
        const request = MutableHashMap.get(idMap, id);
        if (request._tag === "None") {
          continue;
        }
        MutableHashMap.remove(idMap, id);
        request.value.completeUnsafe(Exit.succeed(result));
      }
      if (MutableHashMap.isEmpty(idMap)) {
        return;
      }
      MutableHashMap.forEach(idMap, request => {
        request.completeUnsafe(constNoSuchElement);
      });
    })
  });
};
const void_ = options => RequestResolver.makeGrouped({
  key: transactionKey,
  resolver: Effect.fnUntraced(function* (entries) {
    const inputs = yield* partitionRequests(entries, options.Request);
    yield* options.execute(inputs).pipe(Effect.provideContext(entries[0].context));
    for (let i = 0; i < entries.length; i++) {
      entries[i].completeUnsafe(Exit.void);
    }
  })
});
export {
/**
 * Create a resolver that performs side effects.
 *
 * @since 4.0.0
 * @category resolvers
 */
void_ as void };
const constNoSuchElement = /*#__PURE__*/Exit.fail(/*#__PURE__*/new Cause.NoSuchElementError());
const partitionRequests = function* (requests, schema) {
  const len = requests.length;
  const inputs = Arr.empty();
  let entry;
  const encode = Schema.encodeEffect(schema);
  const handle = Effect.matchCauseEager({
    onFailure(cause) {
      entry.completeUnsafe(Exit.failCause(cause));
    },
    onSuccess(value) {
      inputs.push(value);
    }
  });
  for (let i = 0; i < len; i++) {
    entry = requests[i];
    yield Effect.provideContext(handle(encode(entry.request.payload)), entry.context);
  }
  return inputs;
};
const partitionRequestsById = function* (requests, schema) {
  const len = requests.length;
  const inputs = Arr.empty();
  const byIdMap = MutableHashMap.empty();
  let entry;
  const encode = Schema.encodeEffect(schema);
  const handle = Effect.matchCauseEager({
    onFailure(cause) {
      entry.completeUnsafe(Exit.failCause(cause));
    },
    onSuccess(value) {
      inputs.push(value);
    }
  });
  for (let i = 0; i < len; i++) {
    entry = requests[i];
    yield Effect.provideContext(handle(encode(entry.request.payload)), entry.context);
    MutableHashMap.set(byIdMap, entry.request.payload, entry);
  }
  return [inputs, byIdMap];
};
function transactionKey(entry) {
  const client = entry.context.mapUnsafe.get(SqlClient.SqlClient.key);
  if (!client) return undefined;
  const conn = entry.context.mapUnsafe.get(client.transactionService.key);
  if (!conn) return undefined;
  return Equal.byReferenceUnsafe(conn);
}
//# sourceMappingURL=SqlResolver.js.map