// The same lock shape as repro/zig/main.zig, transliterated to Go:
// second mutex taken inside `if low != high`, deferred there too.
package main

import (
	"fmt"
	"sync"
)

type account struct {
	mu      sync.Mutex
	balance int64
}

type ledger struct {
	accounts [2]account
}

func (l *ledger) deposit(id int, amount int64) {
	a := &l.accounts[id]
	a.mu.Lock()
	defer a.mu.Unlock()
	before := a.balance
	a.balance = before + amount
}

func (l *ledger) transfer(from, to int, amount int64) {
	low := &l.accounts[min(from, to)]
	high := &l.accounts[max(from, to)]
	low.mu.Lock()
	defer low.mu.Unlock()
	if low != high {
		high.mu.Lock()
		defer high.mu.Unlock()
	}
	src := l.accounts[from].balance
	dst := l.accounts[to].balance
	l.accounts[from].balance = src - amount
	l.accounts[to].balance = dst + amount
}

const n = 1_000_000

func main() {
	l := &ledger{}
	l.accounts[0].balance = 1_000_000
	l.accounts[1].balance = 1_000_000
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		for range n {
			l.transfer(0, 1, 1)
		}
	}()
	go func() {
		defer wg.Done()
		for range n {
			l.deposit(1, 1)
		}
	}()
	wg.Wait()
	want := int64(1_000_000 + 2*n)
	got := l.accounts[1].balance
	fmt.Printf("go: account 1 = %d, want %d, missing %d\n", got, want, want-got)
}
