package main

import (
	"fmt"
	"sync"
)

// ===== Event: kontrak data yang disepakati penerbit dan pelanggan =====

type PesananDibuat struct {
	Nomor   string
	Pembeli string
	Total   int
}

type Pelanggan func(e PesananDibuat)

// ===== Bus in-memory: daftar langganan per topik =====

type Bus struct {
	mu        sync.Mutex
	langganan map[string][]Pelanggan
}

func BusBaru() *Bus {
	return &Bus{langganan: map[string][]Pelanggan{}}
}

func (b *Bus) Berlangganan(topik string, fn Pelanggan) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.langganan[topik] = append(b.langganan[topik], fn)
}

func (b *Bus) Terbitkan(topik string, e PesananDibuat) {
	b.mu.Lock()
	// salin agar pelanggan baru tidak bisa mengubah iterasi ini.
	fns := append([]Pelanggan(nil), b.langganan[topik]...)
	b.mu.Unlock()
	for _, fn := range fns {
		fn(e) // pemanggilan sinkron: urutan tetap deterministik
	}
}

// ===== Penerbit: layanan pesanan tidak tahu siapa pelanggannya =====

func terbitkanPesanan(b *Bus, nomor, pembeli string, total int) {
	b.Terbitkan("pesanan.dibuat", PesananDibuat{
		Nomor:   nomor,
		Pembeli: pembeli,
		Total:   total,
	})
}

func main() {
	bus := BusBaru()

	// modul email berlangganan topik pesanan.dibuat.
	bus.Berlangganan("pesanan.dibuat", func(e PesananDibuat) {
		fmt.Printf("[email] %s: pesanan Anda %s sebesar Rp%d sedang diproses\n", e.Pembeli, e.Nomor, e.Total)
	})

	// modul audit berlangganan topik yang sama.
	bus.Berlangganan("pesanan.dibuat", func(e PesananDibuat) {
		fmt.Printf("[audit] mencatat pesanan %s di jejak audit\n", e.Nomor)
	})

	terbitkanPesanan(bus, "PSN-101", "budi@example.com", 250000)
	terbitkanPesanan(bus, "PSN-102", "siti@example.com", 99000)
}
