export type SideEffectJournalState = "started" | "completed";

export interface SideEffectJournalEntry<T> {
  readonly key: string;
  readonly state: SideEffectJournalState;
  readonly result?: T;
}

export interface SideEffectJournal<T> {
  get(key: string): Promise<SideEffectJournalEntry<T> | undefined>;
  markStarted(key: string): Promise<void>;
  markCompleted(key: string, result: T): Promise<void>;
}

export interface IdempotentSideEffect<T> {
  readonly key: string;
  execute(): Promise<T>;
  reconcile(): Promise<T | undefined>;
}

/**
 * Coordinates a non-transactional provider side effect with a durable journal.
 *
 * If a crash occurs after the provider accepted the mutation but before the
 * result was journaled, the next execution reconciles first instead of blindly
 * issuing the mutation again.
 */
export async function runIdempotentSideEffect<T>(
  journal: SideEffectJournal<T>,
  effect: IdempotentSideEffect<T>,
): Promise<T> {
  const entry = await journal.get(effect.key);
  if (entry?.state === "completed" && entry.result !== undefined) return entry.result;

  if (entry?.state === "started") {
    const reconciled = await effect.reconcile();
    if (reconciled !== undefined) {
      await journal.markCompleted(effect.key, reconciled);
      return reconciled;
    }
  } else {
    await journal.markStarted(effect.key);
  }

  const result = await effect.execute();
  await journal.markCompleted(effect.key, result);
  return result;
}
