import * as Cause from "../../Cause.js";
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import * as Effectable from "../../Effectable.js";
import { dual } from "../../Function.js";
import * as Schedule from "../../Schedule.js";
import * as Schema from "../../Schema.js";
import * as DurableDeferred from "./DurableDeferred.js";
import { makeHashDigest } from "./internal/crypto.js";
import * as Workflow from "./Workflow.js";
const TypeId = "~effect/workflow/Activity";
/**
 * @since 4.0.0
 * @category Constructors
 */
export const make = options => {
  const successSchema = options.success ?? Schema.Void;
  const errorSchema = options.error ?? Schema.Never;
  const successSchemaJson = Schema.toCodecJson(successSchema);
  const errorSchemaJson = Schema.toCodecJson(errorSchema);
  // oxlint-disable-next-line prefer-const
  let execute;
  const executeWithoutInterrupt = retryOnInterrupt(options.name, options.interruptRetryPolicy)(options.execute);
  const self = {
    ...Effectable.Prototype({
      label: "Activity",
      evaluate(_) {
        return execute;
      }
    }),
    [TypeId]: TypeId,
    name: options.name,
    successSchema,
    errorSchema,
    exitSchema: Schema.Exit(successSchemaJson, errorSchemaJson, Schema.Defect),
    annotations: options.annotations ?? Context.empty(),
    annotate(tag, value) {
      return make({
        ...options,
        annotations: Context.add(self.annotations, tag, value)
      });
    },
    annotateMerge(context) {
      return make({
        ...options,
        annotations: Context.merge(self.annotations, context)
      });
    },
    execute: executeWithoutInterrupt,
    executeEncoded: Effect.matchEffect(executeWithoutInterrupt, {
      onFailure: error => Effect.flatMap(Effect.orDie(Schema.encodeEffect(errorSchemaJson)(error)), Effect.fail),
      onSuccess: value => Effect.orDie(Schema.encodeEffect(successSchemaJson)(value))
    })
  };
  execute = makeExecute(self);
  return self;
};
const interruptRetryPolicy = /*#__PURE__*/Schedule.exponential(4.0, 1.5).pipe(/*#__PURE__*/Schedule.either(/*#__PURE__*/Schedule.spaced("10 seconds")), /*#__PURE__*/Schedule.either(/*#__PURE__*/Schedule.recurs(10)), /*#__PURE__*/Schedule.satisfiesInputType(), /*#__PURE__*/Schedule.while(meta => Effect.succeed(Cause.hasInterrupts(meta.input))));
const retryOnInterrupt = (name, policy = interruptRetryPolicy) => effect => effect.pipe(Effect.sandbox, Effect.retry(policy), Effect.catch(cause => {
  if (!Cause.hasInterrupts(cause)) return Effect.failCause(cause);
  return Effect.die(`Activity "${name}" interrupted and retry attempts exhausted`);
}));
/**
 * @since 4.0.0
 * @category Error handling
 */
export const retry = /*#__PURE__*/dual(2, (effect, options) => Effect.suspend(() => {
  let attempt = 1;
  return Effect.suspend(() => Effect.provideService(effect, CurrentAttempt, attempt++)).pipe(Effect.retry(options));
}));
/**
 * @since 4.0.0
 * @category Attempts
 */
export const CurrentAttempt = /*#__PURE__*/Context.Reference("effect/workflow/Activity/CurrentAttempt", {
  defaultValue: () => 1
});
/**
 * @since 4.0.0
 * @category Idempotency
 */
export const idempotencyKey = /*#__PURE__*/Effect.fnUntraced(function* (name, options) {
  const instance = yield* InstanceTag;
  let key = `${instance.executionId}`;
  if (options?.includeAttempt) {
    const attempt = yield* CurrentAttempt;
    key += `-${attempt}`;
  }
  key += `-${name}`;
  return yield* makeHashDigest(key);
});
/**
 * @since 4.0.0
 * @category Racing
 */
export const raceAll = (name, activities) => DurableDeferred.raceAll({
  name: `Activity/${name}`,
  success: Schema.Union(activities.map(activity => activity.successSchema)),
  error: Schema.Union(activities.map(activity => activity.errorSchema)),
  effects: activities.map(activity => activity.asEffect())
});
// -----------------------------------------------------------------------------
// internal
// -----------------------------------------------------------------------------
const EngineTag = /*#__PURE__*/Context.Service("effect/workflow/WorkflowEngine");
const InstanceTag = /*#__PURE__*/Context.Service("effect/workflow/WorkflowEngine/WorkflowInstance");
const makeExecute = /*#__PURE__*/Effect.fnUntraced(function* (activity) {
  const engine = yield* EngineTag;
  const instance = yield* InstanceTag;
  const attempt = yield* CurrentAttempt;
  yield* Effect.annotateCurrentSpan({
    executionId: instance.executionId
  });
  const result = yield* Workflow.wrapActivityResult(engine.activityExecute(activity, attempt), _ => _._tag === "Suspended");
  if (result._tag === "Suspended") {
    return yield* Workflow.suspend(instance);
  }
  return yield* result.exit;
}, (effect, activity) => Effect.withSpan(effect, activity.name, {
  captureStackTrace: false
}));
//# sourceMappingURL=Activity.js.map