import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

/** Saga dengan orchestrator: langkah maju, kompensasi berjalan terbalik saat gagal. */
public class SagaPesanan {

    /** Sinyal bahwa satu langkah saga gagal dan kompensasi harus berjalan. */
    static class LangkahGagal extends RuntimeException {
        LangkahGagal(String pesan) {
            super(pesan);
        }
    }

    /** Satu langkah saga: aksi maju beserta aksi kompensasinya. */
    record Langkah(String nama, Runnable maju, Runnable kompensasi) {}

    /** Keadaan seluruh "service" dalam satu dunia simulasi. */
    static class Dunia {
        int stok = 3;
        String pesanan = "BARU";
        final List<String> jejak = new ArrayList<>();

        void catat(String kejadian) {
            jejak.add(kejadian);
        }
    }

    public static void main(String[] args) {
        System.out.println("== Skenario 1: seluruh langkah sukses ==");
        jalankan(0);

        System.out.println();
        System.out.println("== Skenario 2: kartu ditolak di langkah ke-3 ==");
        jalankan(3);

        System.out.println();
        System.out.println("== Skenario 3: email konfirmasi gagal di langkah terakhir ==");
        jalankan(4);
    }

    /** Menjalankan saga; langkah bernomor `gagalDi` melempar LangkahGagal (0 = tanpa kegagalan). */
    static void jalankan(int gagalDi) {
        Dunia dunia = new Dunia();

        List<Langkah> saga = List.of(
                new Langkah("catat pesanan",
                        () -> {
                            dunia.pesanan = "TERCATAT";
                            dunia.catat("maju        : pesanan TERCATAT");
                        },
                        () -> {
                            dunia.pesanan = "DIBATALKAN";
                            dunia.catat("kompensasi  : pesanan DIBATALKAN");
                        }),
                new Langkah("kurangi stok",
                        () -> dunia.catat("maju        : stok jadi " + --dunia.stok),
                        () -> dunia.catat("kompensasi  : stok kembali " + ++dunia.stok)),
                new Langkah("tagih kartu",
                        () -> {
                            if (gagalDi == 3) {
                                throw new LangkahGagal("kartu ditolak");
                            }
                            dunia.catat("maju        : kartu ditagih");
                        },
                        () -> dunia.catat("kompensasi  : dana dikembalikan")),
                new Langkah("kirim konfirmasi",
                        () -> {
                            if (gagalDi == 4) {
                                throw new LangkahGagal("layanan email tidak tersedia");
                            }
                            dunia.catat("maju        : konfirmasi terkirim");
                        },
                        () -> dunia.catat("kompensasi  : konfirmasi dibatalkan")));

        Deque<Runnable> kompensasiTertunda = new ArrayDeque<>();
        String hasil = "SELESAI";
        try {
            for (Langkah l : saga) {
                l.maju().run();                    // transaksi lokal: kommit sendiri
                kompensasiTertunda.push(l.kompensasi()); // daftar tunggu kompensasi
            }
        } catch (LangkahGagal e) {
            hasil = "GAGAL (" + e.getMessage() + ")";
            while (!kompensasiTertunda.isEmpty()) {
                kompensasiTertunda.pop().run();   // urutan terbalik: LIFO
            }
        }

        for (String j : dunia.jejak) {
            System.out.println("  " + j);
        }
        System.out.println("  hasil: " + hasil + ", pesanan=" + dunia.pesanan
                + ", stok=" + dunia.stok);
    }
}
