package main

import (
	"errors"
	"fmt"
	"sync"
)

type Event struct {
	ID     string
	Amount int
}
type Store struct {
	mu     sync.Mutex
	orders map[string]int
	outbox []Event
}

func (s *Store) Create(id string, amount int) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if amount <= 0 {
		return errors.New("invalid amount")
	}
	if old, ok := s.orders[id]; ok {
		if old != amount {
			return errors.New("id conflict")
		}
		return nil
	}
	// One memory critical section models a local atomic transaction, not durability.
	s.orders[id] = amount
	s.outbox = append(s.outbox, Event{id, amount})
	return nil
}

type Sink struct {
	mu    sync.Mutex
	seen  map[string]int
	Total int
}

func (s *Sink) Apply(e Event) (bool, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if old, ok := s.seen[e.ID]; ok {
		if old != e.Amount {
			return false, errors.New("id conflict")
		}
		return false, nil
	}
	// Deduplication record and business effect are changed atomically in this model.
	s.Total += e.Amount
	s.seen[e.ID] = e.Amount
	return true, nil
}
func main() {
	s := Store{orders: map[string]int{}}
	must(s.Create("o-1", 10))
	must(s.Create("o-1", 10))
	fmt.Println("orders:", len(s.orders), "outbox:", len(s.outbox))
	sink := Sink{seen: map[string]int{}}
	// Relay crashes after first delivery and before marking the row sent.
	first, e := sink.Apply(s.outbox[0])
	must(e)
	again, e := sink.Apply(s.outbox[0])
	must(e)
	fmt.Println("delivery 1:", first, "delivery 2:", again, "effect total:", sink.Total)
	_, e = sink.Apply(Event{"o-1", 99})
	fmt.Println("same id different payload:", e)
	broken := Sink{seen: map[string]int{"o-1": 10}} // crashed after marking seen, before effect
	applied, e := broken.Apply(Event{"o-1", 10})
	must(e)
	fmt.Println("split dedupe/effect after crash:", applied, "total:", broken.Total)
}
func must(e error) {
	if e != nil {
		panic(e)
	}
}
