admin.gno

package users

import (
	"chain"
	"chain/runtime"

	"gno.land/p/moul/addrset"
	"gno.land/p/nt/ufmt/v0"

	"gno.land/r/gov/dao"
)

const initControllerPath = "gno.land/r/sys/users/init"

var controllers = addrset.Set{} // caller whitelist

func init() {
	// auto-whitelist the init controller for bootstrapping for testing chain.
	if chainID := runtime.ChainID(); chainID == "dev" {
		controllers.Add(chain.PackageAddress(initControllerPath))
	}
}

// AddControllerAtGenesis allows adding a controller during chain genesis (height 0).
// This is mostly useful for testing.
func AddControllerAtGenesis(_ realm, addr address) {
	height := runtime.ChainHeight()
	if height > 0 {
		panic("AddControllerAtGenesis can only be called at genesis (height 0)")
	}

	if !addr.IsValid() {
		panic(ErrInvalidAddress)
	}

	controllers.Add(addr)
}

// ProposeNewController allows GovDAO to add a whitelisted caller
func ProposeNewController(addr address) dao.ProposalRequest {
	if !addr.IsValid() {
		panic(ErrInvalidAddress)
	}

	cb := func(cur realm) error {
		return addToWhitelist(addr)
	}

	desc := "This proposal adds " + addr.String() + " to `sys/users` realm's callers whitelist."
	return dao.NewProposalRequest("Add Whitelisted Caller to \"sys/users\" Realm", desc, dao.NewSimpleExecutor(cb, ""))
}

// ProposeControllerRemoval allows GovDAO to add a whitelisted caller
func ProposeControllerRemoval(addr address) dao.ProposalRequest {
	if !addr.IsValid() {
		panic(ErrInvalidAddress)
	}

	cb := func(cur realm) error {
		return deleteFromWhitelist(addr)
	}

	desc := "This proposal removes " + addr.String() + " from `sys/users` realm's callers whitelist."
	return dao.NewProposalRequest("Remove Whitelisted Caller From \"sys/users\" Realm", desc, dao.NewSimpleExecutor(cb, ""))
}

// ProposeControllerAdditionAndRemoval allows GovDAO to add a new caller and remove an old caller in the same proposal.
func ProposeControllerAdditionAndRemoval(toAdd, toRemove address) dao.ProposalRequest {
	if !toAdd.IsValid() || !toRemove.IsValid() {
		panic(ErrInvalidAddress)
	}

	cb := func(cur realm) error {
		err := addToWhitelist(toAdd)
		if err != nil {
			return err
		}

		return deleteFromWhitelist(toRemove)
	}

	desc := ufmt.Sprint(
		"This proposal adds %s and removes %s from `sys/users` realm's callers whitelist.",
		toAdd,
		toRemove,
	)
	return dao.NewProposalRequest("Add and Remove Whitelisted Callers From \"sys/users\" Realm", desc, dao.NewSimpleExecutor(cb, ""))
}

// Helpers

func deleteFromWhitelist(addr address) error {
	if !controllers.Has(addr) {
		return NewErrNotWhitelisted()
	}

	if ok := controllers.Remove(addr); !ok {
		return ErrWhitelistRemoveFailed
	}

	return nil
}

func addToWhitelist(newCaller address) error {
	if !controllers.Add(newCaller) {
		return ErrAlreadyWhitelisted
	}

	return nil
}