package chanfunding

import (
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)

// CoinSource is an interface that allows a caller to access a source of UTXOs
// to use when attempting to fund a new channel.
type CoinSource interface {
	// ListCoins returns all UTXOs from the source that have between
	// minConfs and maxConfs number of confirmations.
	ListCoins(minConfs, maxConfs int32) ([]Coin, error)

	// CoinFromOutPoint attempts to locate details pertaining to a coin
	// based on its outpoint. If the coin isn't under the control of the
	// backing CoinSource, then an error should be returned.
	CoinFromOutPoint(wire.OutPoint) (*Coin, error)
}

// CoinSelectionLocker is an interface that allows the caller to perform an
// operation, which is synchronized with all coin selection attempts. This can
// be used when an operation requires that all coin selection operations cease
// forward progress. Think of this as an exclusive lock on coin selection
// operations.
type CoinSelectionLocker interface {
	// WithCoinSelectLock will execute the passed function closure in a
	// synchronized manner preventing any coin selection operations from
	// proceeding while the closure is executing. This can be seen as the
	// ability to execute a function closure under an exclusive coin
	// selection lock.
	WithCoinSelectLock(func() error) error
}

// OutpointLocker allows a caller to lock/unlock an outpoint. When locked, the
// outpoints shouldn't be used for any sort of channel funding of coin
// selection. Locked outpoints are not expected to be persisted between
// restarts.
type OutpointLocker interface {
	// LockOutpoint locks a target outpoint, rendering it unusable for coin
	// selection.
	LockOutpoint(o wire.OutPoint)

	// UnlockOutpoint unlocks a target outpoint, allowing it to be used for
	// coin selection once again.
	UnlockOutpoint(o wire.OutPoint)
}

// Request is a new request for funding a channel. The items in the struct
// governs how the final channel point will be provisioned by the target
// Assembler.
type Request struct {
	// LocalAmt is the amount of coins we're placing into the funding
	// output.
	LocalAmt btcutil.Amount

	// RemoteAmt is the amount of coins the remote party is contributing to
	// the funding output.
	RemoteAmt btcutil.Amount

	// MinConfs controls how many confirmations a coin need to be eligible
	// to be used as an input to the funding transaction. If this value is
	// set to zero, then zero conf outputs may be spent.
	MinConfs int32

	// SubtractFees should be set if we intend to spend exactly LocalAmt
	// when opening the channel, subtracting the fees from the funding
	// output. This can be used for instance to use all our remaining funds
	// to open the channel, since it will take fees into
	// account.
	SubtractFees bool

	// FeeRate is the fee rate in sat/kw that the funding transaction
	// should carry.
	FeeRate chainfee.SatPerKWeight

	// ChangeAddr is a closure that will provide the Assembler with a
	// change address for the funding transaction if needed.
	ChangeAddr func() (btcutil.Address, error)
}

// Intent is returned by an Assembler and represents the base functionality the
// caller needs to proceed with channel funding on a higher level. If the
// Cancel method is called, then all resources assembled to fund the channel
// will be released back to the eligible pool.
type Intent interface {
	// FundingOutput returns the witness script, and the output that
	// creates the funding output.
	FundingOutput() ([]byte, *wire.TxOut, error)

	// ChanPoint returns the final outpoint that will create the funding
	// output described above.
	ChanPoint() (*wire.OutPoint, error)

	// RemoteFundingAmt is the amount the remote party put into the
	// channel.
	RemoteFundingAmt() btcutil.Amount

	// LocalFundingAmt is the amount we put into the channel. This may
	// differ from the local amount requested, as depending on coin
	// selection, we may bleed from of that LocalAmt into fees to minimize
	// change.
	LocalFundingAmt() btcutil.Amount

	// Cancel allows the caller to cancel a funding Intent at any time.
	// This will return any resources such as coins back to the eligible
	// pool to be used in order channel fundings.
	Cancel()
}

// Assembler is an abstract object that is capable of assembling everything
// needed to create a new funding output. As an example, this assembler may be
// our core backing wallet, an interactive PSBT based assembler, an assembler
// than can aggregate multiple intents into a single funding transaction, or an
// external protocol that creates a funding output out-of-band such as channel
// factories.
type Assembler interface {
	// ProvisionChannel returns a populated Intent that can be used to
	// further the channel funding workflow. Depending on the
	// implementation of Assembler, additional state machine (Intent)
	// actions may be required before the FundingOutput and ChanPoint are
	// made available to the caller.
	ProvisionChannel(*Request) (Intent, error)
}

// FundingTxAssembler is a super-set of the regular Assembler interface that's
// also able to provide a fully populated funding transaction via the intents
// that it produces.
type FundingTxAssembler interface {
	Assembler

	// FundingTxAvailable is an empty method that an assembler can
	// implement to signal to callers that its able to provide the funding
	// transaction for the channel via the intent it returns.
	FundingTxAvailable()
}
