import { bcs } from '@mysten/sui/bcs'
import { SuiClient } from '@mysten/sui/client'
import { Transaction, TransactionArgument, TransactionResult } from '@mysten/sui/transactions'

import { OAppInfoV1Bcs } from '../../bcs'
import { ModuleManager } from '../../module-manager'
import { ObjectOptions } from '../../types'
import { OAppInfoV1 } from '../../types/oapp'
import {
    asAddress,
    asBytes,
    asBytes32,
    asObject,
    asU16,
    asU32,
    asU64,
    executeSimulate,
    isTransactionArgument,
} from '../../utils'

const MODULE_NAME = 'oapp'

export const OAppErrorCode = {
    // OApp related errors (matching oapp.move)
    OApp_EInvalidAdminCap: 1,
    OApp_EInvalidOAppCap: 2,
    OApp_EInvalidRefundAddress: 3,
    OApp_EInvalidSendingCall: 4,
    OApp_EOnlyEndpoint: 5,
    OApp_EOnlyPeer: 6,
    OApp_ESendingInProgress: 7,

    // OAppPeer related errors (matching oapp_peer.move)
    OAppPeer_EPeerNotFound: 0,
    OAppPeer_EInvalidPeer: 1,

    // EnforcedOptions related errors (matching enforced_options.move)
    EnforcedOptions_EEnforcedOptionsNotFound: 1,
    EnforcedOptions_EInvalidOptionsLength: 2,
    EnforcedOptions_EInvalidOptionsType: 3,

    // OAppInfoV1 related errors (matching oapp_info_v1.move)
    OAppInfoV1_EInvalidData: 1,
    OAppInfoV1_EInvalidVersion: 2,
} as const

export class OApp {
    public packageId: string
    public oappCallCapId: string
    public readonly client: SuiClient
    public oappInfo: OAppInfoV1 | null = null
    private readonly objects: ObjectOptions

    constructor(
        packageId: string,
        oappCallCapId: string,
        client: SuiClient,
        objects: ObjectOptions,
        private readonly moduleManager: ModuleManager
    ) {
        this.packageId = packageId
        this.oappCallCapId = oappCallCapId
        this.client = client
        this.objects = objects
    }

    // === Set Functions ===

    /**
     * Set enforced options for OApp messaging
     * @param tx - The transaction to add the move call to
     * @param eid - Endpoint ID or transaction argument
     * @param msgType - Message type or transaction argument
     * @param options - Enforced options as bytes or transaction argument
     */
    async setEnforcedOptionsMoveCall(
        tx: Transaction,
        eid: number | TransactionArgument,
        msgType: number | TransactionArgument,
        options: Uint8Array | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        tx.moveCall({
            target: this.#target('set_enforced_options'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asU32(tx, eid),
                asU16(tx, msgType),
                asBytes(tx, options),
            ],
        })
    }

    /**
     * Set peer OApp on another chain
     * @param tx - The transaction to add the move call to
     * @param eid - Peer endpoint ID or transaction argument
     * @param peer - Peer OApp address as bytes or transaction argument
     */
    async setPeerMoveCall(
        tx: Transaction,
        eid: number | TransactionArgument,
        peer: Uint8Array | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId)
        tx.moveCall({
            target: this.#target('set_peer'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asObject(tx, messagingChannel),
                asU32(tx, eid),
                asBytes32(tx, peer, this.moduleManager.getUtils()),
            ],
        })
    }

    // === View Functions ===

    /**
     * Get admin capability address for OApp
     * @param tx - The transaction to add the move call to
     * @param oapp - The OApp object ID or transaction argument
     * @returns Transaction result containing the admin capability address
     */
    getAdminCapMoveCall(tx: Transaction, oapp: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('admin_cap'),
            arguments: [asObject(tx, oapp)],
        })
    }

    /**
     * Get admin capability address for OApp
     * @param oapp - The OApp object ID
     * @returns Promise<string> - The admin capability address
     */
    async getAdminCap(oapp: string): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.getAdminCapMoveCall(tx, oapp)
            },
            (result) => bcs.Address.parse(result[0].value)
        )
    }

    /**
     * Get OApp CallCap identifier
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the OApp CallCap identifier
     */
    async getOAppCapIdMoveCall(tx: Transaction): Promise<TransactionResult> {
        const oappInfo = await this.#oappInfo()
        return tx.moveCall({
            target: this.#target('oapp_cap_id'),
            arguments: [asObject(tx, oappInfo.oapp_object)],
        })
    }

    /**
     * Get OApp CallCap identifier
     * @returns Promise<string> - The OApp CallCap identifier
     */
    async getOAppCapId(): Promise<string> {
        return executeSimulate(
            this.client,
            async (tx) => {
                await this.getOAppCapIdMoveCall(tx)
            },
            (result) => bcs.Address.parse(result[0].value)
        )
    }

    /**
     * Combine enforced options with extra options for message execution
     * @param tx - The transaction to add the move call to
     * @param eid - Destination endpoint ID or transaction argument
     * @param msgType - Message type or transaction argument
     * @param extraOptions - Extra options to combine with enforced options or transaction argument
     * @returns Transaction result containing combined options
     */
    async combineOptionsMoveCall(
        tx: Transaction,
        eid: number | TransactionArgument,
        msgType: number | TransactionArgument,
        extraOptions: Uint8Array | TransactionArgument
    ): Promise<TransactionResult> {
        const oappInfo = await this.#oappInfo()
        return tx.moveCall({
            target: this.#target('combine_options'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asU32(tx, eid),
                asU16(tx, msgType),
                asBytes(tx, extraOptions),
            ],
        })
    }

    /**
     * Combine enforced options with extra options for message execution
     * @param eid - Destination endpoint ID
     * @param msgType - Message type
     * @param extraOptions - Extra options to combine with enforced options
     * @returns Promise<Uint8Array> - Combined options as bytes
     */
    async combineOptions(eid: number, msgType: number, extraOptions: Uint8Array): Promise<Uint8Array> {
        return executeSimulate(
            this.client,
            async (tx) => {
                await this.combineOptionsMoveCall(tx, eid, msgType, extraOptions)
            },
            (result) => new Uint8Array(bcs.vector(bcs.u8()).parse(result[0].value))
        )
    }

    /**
     * Get enforced options for a specific destination and message type
     * @param tx - The transaction to add the move call to
     * @param eid - Destination endpoint ID or transaction argument
     * @param msgType - Message type or transaction argument
     * @returns Transaction result containing enforced options
     */
    async getEnforcedOptionsMoveCall(
        tx: Transaction,
        eid: number | TransactionArgument,
        msgType: number | TransactionArgument
    ): Promise<TransactionResult> {
        const oappInfo = await this.#oappInfo()
        return tx.moveCall({
            target: this.#target('get_enforced_options'),
            arguments: [asObject(tx, oappInfo.oapp_object), asU32(tx, eid), asU16(tx, msgType)],
        })
    }

    /**
     * Get enforced options for a specific destination and message type
     * @param eid - Destination endpoint ID
     * @param msgType - Message type
     * @returns Promise<Uint8Array> - Enforced options as bytes
     */
    async getEnforcedOptions(eid: number, msgType: number): Promise<Uint8Array> {
        return executeSimulate(
            this.client,
            async (tx) => {
                await this.getEnforcedOptionsMoveCall(tx, eid, msgType)
            },
            (result) => new Uint8Array(bcs.vector(bcs.u8()).parse(result[0].value))
        )
    }

    /**
     * Check if a peer is configured for a specific destination chain
     * @param tx - The transaction to add the move call to
     * @param dstEid - Destination endpoint ID or transaction argument
     * @returns Transaction result containing boolean result
     */
    async hasPeerMoveCall(tx: Transaction, dstEid: number | TransactionArgument): Promise<TransactionResult> {
        const oappInfo = await this.#oappInfo()
        return tx.moveCall({
            target: this.#target('has_peer'),
            arguments: [asObject(tx, oappInfo.oapp_object), asU32(tx, dstEid)],
        })
    }

    /**
     * Check if a peer is configured for a specific destination chain
     * @param dstEid - Destination endpoint ID
     * @returns Promise<boolean> - True if peer is configured, false otherwise
     */
    async hasPeer(dstEid: number): Promise<boolean> {
        return executeSimulate(
            this.client,
            async (tx) => {
                await this.hasPeerMoveCall(tx, dstEid)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Get the configured peer address for a specific destination chain
     * @param tx - The transaction to add the move call to
     * @param dstEid - Destination endpoint ID or transaction argument
     * @returns Transaction result containing peer address
     */
    async getPeerMoveCall(tx: Transaction, dstEid: number | TransactionArgument): Promise<TransactionResult> {
        const oappInfo = await this.#oappInfo()
        return tx.moveCall({
            target: this.#target('get_peer'),
            arguments: [asObject(tx, oappInfo.oapp_object), asU32(tx, dstEid)],
        })
    }

    /**
     * Get the configured peer address for a specific destination chain
     * @param dstEid - Destination endpoint ID
     * @returns Promise<Uint8Array> - Peer address as bytes32
     */
    async getPeer(dstEid: number): Promise<Uint8Array> {
        return executeSimulate(
            this.client,
            async (tx) => {
                await this.getPeerMoveCall(tx, dstEid)
            },
            (result) => {
                return new Uint8Array(bcs.vector(bcs.u8()).parse(result[0].value))
            }
        )
    }

    /**
     * Get OApp information V1 structure
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the OApp information V1
     */
    getOAppInfoV1MoveCall(tx: Transaction): TransactionResult {
        const endpoint = this.moduleManager.getEndpoint()
        const oappInfoRaw = endpoint.getOappInfoMoveCall(tx, this.oappCallCapId) // the new OAppInfo used to be named as lz_receive_info
        return tx.moveCall({
            target: this.#target('decode', 'oapp_info_v1'),
            arguments: [oappInfoRaw],
        })
    }

    /**
     * Get OApp information V1 structure
     * @returns Promise<OAppInfoV1> - The OApp information V1
     */
    async getOAppInfoV1(): Promise<OAppInfoV1> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.getOAppInfoV1MoveCall(tx)
            },
            (result) => {
                return OAppInfoV1Bcs.parse(result[0].value)
            }
        )
    }

    getOAppInfoV1ExtraInfoMoveCall(tx: Transaction, oappInfo: TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('extra_info', 'oapp_info_v1'),
            arguments: [oappInfo],
        })
    }

    // === Endpoint Call Functions ===

    /**
     * Register OApp with the endpoint
     * @param tx - The transaction to add the move call to
     * @param oappObjectId - The OApp object ID
     * @param oappInfo - OApp information as bytes (optional)
     * @returns Transaction result containing the messaging channel address
     */
    async registerOAppMoveCall(
        tx: Transaction,
        oappObjectId: string,
        oappInfo?: Uint8Array | TransactionArgument
    ): Promise<TransactionResult> {
        let oappInfoArg: TransactionArgument
        if (isTransactionArgument(oappInfo)) {
            oappInfoArg = oappInfo
        } else if (oappInfo) {
            // For Some(vector<u8>), convert Uint8Array to number[] and wrap in Option
            oappInfoArg = tx.pure.option('vector<u8>', Array.from(oappInfo))
        } else {
            // For None
            oappInfoArg = tx.pure.option('vector<u8>', null)
        }
        const adminCap = await this.getAdminCap(oappObjectId)
        return tx.moveCall({
            target: this.#target('register_oapp', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappObjectId),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                oappInfoArg,
            ],
        })
    }

    /**
     * Set delegate for OApp
     * @param tx - The transaction to add the move call to
     * @param newDelegate - New delegate address
     */
    async setDelegateMoveCall(tx: Transaction, newDelegate: string | TransactionArgument): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        tx.moveCall({
            target: this.#target('set_delegate', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asAddress(tx, newDelegate),
            ],
        })
    }

    /**
     * Set OApp information in the endpoint
     * @param tx - The transaction to add the move call to
     * @param oappInfo - OApp information as bytes
     * @param oappObjectId - Optional OApp object ID (uses configured oapp if not provided)
     */
    async setOAppInfoMoveCall(
        tx: Transaction,
        oappInfo: Uint8Array | TransactionArgument,
        oappObjectId?: string
    ): Promise<void> {
        if (oappObjectId === undefined) {
            const oappInfoObj = await this.#oappInfo()
            oappObjectId = oappInfoObj.oapp_object
        }
        const adminCap = await this.getAdminCap(oappObjectId)
        tx.moveCall({
            target: this.#target('set_oapp_info', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappObjectId),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asBytes(tx, oappInfo),
            ],
        })
    }

    /**
     * Initialize channel with remote OApp
     * @param tx - The transaction to add the move call to
     * @param remoteEid - Remote endpoint ID
     * @param remoteOApp - Remote OApp address as bytes32
     */
    async initChannelMoveCall(
        tx: Transaction,
        remoteEid: number | TransactionArgument,
        remoteOApp: Uint8Array | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId)
        tx.moveCall({
            target: this.#target('init_channel', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asObject(tx, messagingChannel),
                asU32(tx, remoteEid),
                asBytes32(tx, remoteOApp, this.moduleManager.getUtils()),
            ],
        })
    }

    /**
     * Clear a message from the messaging channel
     * @param tx - The transaction to add the move call to
     * @param srcEid - Source endpoint ID
     * @param sender - Sender address as bytes32
     * @param nonce - Message nonce
     * @param guid - Message GUID as bytes32
     * @param message - Message payload
     */
    async clearMoveCall(
        tx: Transaction,
        srcEid: number | TransactionArgument,
        sender: Uint8Array | TransactionArgument,
        nonce: number | bigint | TransactionArgument,
        guid: Uint8Array | TransactionArgument,
        message: Uint8Array | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId)
        tx.moveCall({
            target: this.#target('clear', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asObject(tx, messagingChannel),
                asU32(tx, srcEid),
                asBytes32(tx, sender, this.moduleManager.getUtils()),
                asU64(tx, nonce),
                asBytes32(tx, guid, this.moduleManager.getUtils()),
                asBytes(tx, message),
            ],
        })
    }

    /**
     * Skip a message in the messaging channel
     * @param tx - The transaction to add the move call to
     * @param srcEid - Source endpoint ID
     * @param sender - Sender address as bytes32
     * @param nonce - Message nonce
     */
    async skipMoveCall(
        tx: Transaction,
        srcEid: number | TransactionArgument,
        sender: Uint8Array | TransactionArgument,
        nonce: number | bigint | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId)
        tx.moveCall({
            target: this.#target('skip', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asObject(tx, messagingChannel),
                asU32(tx, srcEid),
                asBytes32(tx, sender, this.moduleManager.getUtils()),
                asU64(tx, nonce),
            ],
        })
    }

    /**
     * Nilify a message in the messaging channel
     * @param tx - The transaction to add the move call to
     * @param srcEid - Source endpoint ID
     * @param sender - Sender address as bytes32
     * @param nonce - Message nonce
     * @param payloadHash - Payload hash as bytes32
     */
    async nilifyMoveCall(
        tx: Transaction,
        srcEid: number | TransactionArgument,
        sender: Uint8Array | TransactionArgument,
        nonce: number | bigint | TransactionArgument,
        payloadHash: Uint8Array | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId)
        tx.moveCall({
            target: this.#target('nilify', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asObject(tx, messagingChannel),
                asU32(tx, srcEid),
                asBytes32(tx, sender, this.moduleManager.getUtils()),
                asU64(tx, nonce),
                asBytes32(tx, payloadHash, this.moduleManager.getUtils()),
            ],
        })
    }

    /**
     * Burn a message in the messaging channel
     * @param tx - The transaction to add the move call to
     * @param srcEid - Source endpoint ID
     * @param sender - Sender address as bytes32
     * @param nonce - Message nonce
     * @param payloadHash - Payload hash as bytes32
     */
    async burnMoveCall(
        tx: Transaction,
        srcEid: number | TransactionArgument,
        sender: Uint8Array | TransactionArgument,
        nonce: number | bigint | TransactionArgument,
        payloadHash: Uint8Array | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        const messagingChannel = await this.moduleManager.getEndpoint().getMessagingChannel(this.oappCallCapId)
        tx.moveCall({
            target: this.#target('burn', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asObject(tx, messagingChannel),
                asU32(tx, srcEid),
                asBytes32(tx, sender, this.moduleManager.getUtils()),
                asU64(tx, nonce),
                asBytes32(tx, payloadHash, this.moduleManager.getUtils()),
            ],
        })
    }

    /**
     * Set send library for a destination chain
     * @param tx - The transaction to add the move call to
     * @param dstEid - Destination endpoint ID
     * @param sendLibrary - Send library address
     */
    async setSendLibraryMoveCall(
        tx: Transaction,
        dstEid: number | TransactionArgument,
        sendLibrary: string | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        tx.moveCall({
            target: this.#target('set_send_library', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asU32(tx, dstEid),
                asAddress(tx, sendLibrary),
            ],
        })
    }

    /**
     * Set receive library for a source chain
     * @param tx - The transaction to add the move call to
     * @param srcEid - Source endpoint ID
     * @param receiveLibrary - Receive library address
     * @param gracePeriod - Grace period in seconds
     */
    async setReceiveLibraryMoveCall(
        tx: Transaction,
        srcEid: number | TransactionArgument,
        receiveLibrary: string | TransactionArgument,
        gracePeriod: number | bigint | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        tx.moveCall({
            target: this.#target('set_receive_library', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asU32(tx, srcEid),
                asAddress(tx, receiveLibrary),
                asU64(tx, gracePeriod),
                tx.object.clock(),
            ],
        })
    }

    /**
     * Set receive library timeout for a source chain
     * @param tx - The transaction to add the move call to
     * @param srcEid - Source endpoint ID
     * @param receiveLibrary - Receive library address
     * @param expiry - Expiry timestamp in seconds
     */
    async setReceiveLibraryTimeoutMoveCall(
        tx: Transaction,
        srcEid: number | TransactionArgument,
        receiveLibrary: string | TransactionArgument,
        expiry: number | bigint | TransactionArgument
    ): Promise<void> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        tx.moveCall({
            target: this.#target('set_receive_library_timeout', 'endpoint_calls'),
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asU32(tx, srcEid),
                asAddress(tx, receiveLibrary),
                asU64(tx, expiry),
                tx.object.clock(),
            ],
        })
    }

    /**
     * Set configuration for a message library
     * @param tx - The transaction to add the move call to
     * @param lib - Library address
     * @param eid - Endpoint ID
     * @param configType - Configuration type
     * @param config - Configuration data as bytes
     * @returns Transaction result containing Call<MessageLibSetConfigParam, Void>
     */
    async setConfigMoveCall(
        tx: Transaction,
        lib: string | TransactionArgument,
        eid: number | TransactionArgument,
        configType: number | TransactionArgument,
        config: Uint8Array | TransactionArgument
    ): Promise<TransactionResult> {
        const oappInfo = await this.#oappInfo()
        const adminCap = await this.getAdminCap(oappInfo.oapp_object)
        return tx.moveCall({
            target: this.#target('set_config', 'endpoint_calls'),
            typeArguments: [],
            arguments: [
                asObject(tx, oappInfo.oapp_object),
                asObject(tx, adminCap),
                asObject(tx, this.objects.endpointV2),
                asAddress(tx, lib),
                asU32(tx, eid),
                asU32(tx, configType),
                asBytes(tx, config),
            ],
        })
    }

    /**
     * Refresh the cached OApp information by fetching the latest data
     * @returns Promise<void> - Completes when the OApp info is refreshed
     */
    async refreshOAppInfo(): Promise<void> {
        this.oappInfo = await this.getOAppInfoV1()
    }

    // === Private Functions ===

    /**
     * Get OApp info, throwing if not set
     * @returns The OApp info
     * @throws Error if OApp info is not set
     * @private
     */
    async #oappInfo(): Promise<OAppInfoV1> {
        if (!this.oappInfo) {
            this.oappInfo = await this.getOAppInfoV1()
        }
        return this.oappInfo
    }

    /**
     * Generate the full target path for move calls
     * @param name - The function name to call
     * @returns The full module path for the move call
     * @private
     */
    #target(name: string, module_name = MODULE_NAME): string {
        return `${this.packageId}::${module_name}::${name}`
    }
}
