import java.util.ArrayList;
import java.util.List;

public class KeranjangBelanja {
    public static void main(String[] args) {
        // Value object dibandingkan berdasarkan nilai, bukan lokasi objek di memori
        Item bukuA = new Item("Buku Java", 2, 85_000);
        Item bukuB = new Item("Buku Java", 2, 85_000);
        System.out.println("dua item bernilai sama dianggap sama: " + bukuA.equals(bukuB));

        // Keranjang adalah aggregate: satu pintu untuk mengubah seluruh isinya
        Keranjang keranjang = new Keranjang("KR-001");
        keranjang.tambah(bukuA);
        keranjang.tambah(new Item("Pena", 3, 12_000));
        keranjang.tambah(new Item("Buku Java", 1, 85_000)); // digabung, bukan item baru
        System.out.println(keranjang.ringkas());

        // Invarian dijaga aggregate root, bukan kepercayaan pada pemanggil
        try {
            keranjang.ubahJumlah("Pena", 0);
        } catch (IllegalArgumentException e) {
            System.out.println("ditolak aggregate: " + e.getMessage());
        }

        // Entity disamakan lewat identitas, sekalipun isi keranjangnya berbeda
        Keranjang keranjangLain = new Keranjang("KR-001");
        System.out.println("keranjang lain ber-id sama dianggap sama: "
                + keranjang.equals(keranjangLain));
    }
}

// Value object: tanpa identitas, tidak dapat diubah, dibandingkan berdasarkan nilai
record Item(String nama, int jumlah, long hargaSatuan) {
    Item {
        if (nama == null || nama.isBlank()) {
            throw new IllegalArgumentException("nama produk wajib diisi");
        }
        if (jumlah < 1) {
            throw new IllegalArgumentException("jumlah minimal 1, diterima: " + jumlah);
        }
        if (hargaSatuan < 0) {
            throw new IllegalArgumentException("harga tidak boleh negatif");
        }
    }

    long subtotal() {
        return (long) jumlah * hargaSatuan;
    }
}

// Aggregate root: pemilik invarian dan satu-satunya pintu menuju item di dalamnya
class Keranjang {
    private final String id;
    private final List<Item> items = new ArrayList<>();

    Keranjang(String id) {
        if (id == null || id.isBlank()) {
            throw new IllegalArgumentException("id keranjang wajib diisi");
        }
        this.id = id;
    }

    void tambah(Item itemBaru) {
        for (int i = 0; i < items.size(); i++) {
            Item ada = items.get(i);
            if (ada.nama().equals(itemBaru.nama())) {
                items.set(i, new Item(ada.nama(),
                        ada.jumlah() + itemBaru.jumlah(), ada.hargaSatuan()));
                return;
            }
        }
        items.add(itemBaru);
    }

    void ubahJumlah(String nama, int jumlahBaru) {
        if (jumlahBaru < 1) {
            throw new IllegalArgumentException("jumlah item \"" + nama
                    + "\" minimal 1, diterima: " + jumlahBaru);
        }
        for (int i = 0; i < items.size(); i++) {
            if (items.get(i).nama().equals(nama)) {
                Item lama = items.get(i);
                items.set(i, new Item(lama.nama(), jumlahBaru, lama.hargaSatuan()));
                return;
            }
        }
        throw new IllegalArgumentException("item \"" + nama + "\" tidak ada di keranjang");
    }

    long total() {
        long total = 0;
        for (Item item : items) {
            total += item.subtotal();
        }
        return total;
    }

    String ringkas() {
        StringBuilder teks = new StringBuilder("Keranjang " + id + ":");
        for (Item item : items) {
            teks.append("\n  - ").append(item.nama())
                    .append(" x").append(item.jumlah())
                    .append(" = Rp").append(item.subtotal());
        }
        teks.append("\n  total = Rp").append(total());
        return teks.toString();
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof Keranjang lain && lain.id.equals(id);
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }
}
