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

import { DstConfigBcs } from '../../bcs'
import { ModuleManager } from '../../module-manager'
import { DstConfig, NativeDropParams, ObjectOptions } from '../../types'
import {
    asAddress,
    asArgWithTx,
    asBool,
    asBytes,
    asBytes32,
    asObject,
    asU128,
    asU16,
    asU32,
    asU64,
    executeSimulate,
} from '../../utils'

const MODULE_NAME = 'executor_worker'

export const ExecutorErrorCode = {
    // Executor related errors (with Executor_ prefix)
    Executor_EEidNotSupported: 1,
    Executor_EInvalidNativeDropAmount: 2,

    // ExecutorInfoV1 related errors (matching executor_info_v1.move)
    ExecutorInfoV1_EInvalidData: 1,
    ExecutorInfoV1_EInvalidVersion: 2,
} as const

export class Executor {
    public packageId: string
    public readonly client: SuiClient
    private readonly objects: ObjectOptions

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

    // === Witness Functions ===

    /**
     * Create a LayerZero witness for Executor package whitelist registration
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the LayerZero witness
     */
    createLayerZeroWitnessMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: `${this.packageId}::executor_witness::new`,
            arguments: [],
        })
    }

    // Job Assignment and Fee Functions

    /**
     * Assign execution job for Executor (called via PTB with Call created by send function in ULN302)
     * @param tx - The transaction to add the move call to
     * @param call - The call transaction result from ULN302
     * @returns Transaction result containing the job assignment call
     */
    assignJobMoveCall(tx: Transaction, call: TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('assign_job'),
            arguments: [tx.object(this.objects.executor), call],
        })
    }

    /**
     * Confirm assign job operation with fee calculation
     * @param tx - The transaction to add the move call to
     * @param executorCall - The executor call transaction result
     * @param feelibCall - The fee library call transaction result
     */
    confirmAssignJobMoveCall(
        tx: Transaction,
        executorCall: TransactionArgument,
        feelibCall: TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('confirm_assign_job'),
            arguments: [tx.object(this.objects.executor), executorCall, feelibCall],
        })
    }

    /**
     * Get fee for execution (using Call created by quote function in ULN302)
     * @param tx - The transaction to add the move call to
     * @param call - The call transaction result from ULN302
     * @returns Transaction result containing the fee calculation call
     */
    getFeeMoveCall(tx: Transaction, call: TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('get_fee'),
            arguments: [tx.object(this.objects.executor), call],
        })
    }

    /**
     * Confirm get fee operation with fee library
     * @param tx - The transaction to add the move call to
     * @param executorCall - The executor call transaction result
     * @param feelibCall - The fee library call transaction result
     */
    confirmGetFeeMoveCall(tx: Transaction, executorCall: TransactionArgument, feelibCall: TransactionArgument): void {
        tx.moveCall({
            target: this.#target('confirm_get_fee'),
            arguments: [tx.object(this.objects.executor), executorCall, feelibCall],
        })
    }

    // Execution Functions

    /**
     * Execute LayerZero receive operation (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param messagingChannel - The messaging channel object ID
     * @param srcEid - Source endpoint ID
     * @param sender - Sender address as bytes
     * @param nonce - Message nonce
     * @param guid - Globally unique identifier as bytes
     * @param message - Message payload as bytes
     * @param extraData - Additional execution data (optional)
     * @param value - Native token value to transfer
     * @returns Transaction result containing the execution call capability
     */
    executeLzReceiveMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        messagingChannel: string | TransactionArgument,
        srcEid: number | TransactionArgument,
        sender: Uint8Array | TransactionArgument,
        nonce: bigint | number | string | TransactionArgument,
        guid: Uint8Array | TransactionArgument,
        message: Uint8Array | TransactionArgument,
        extraData: Uint8Array | TransactionArgument = new Uint8Array(),
        value = 0n
    ): TransactionResult {
        return tx.moveCall({
            target: this.#target('execute_lz_receive'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, adminCap),
                tx.object(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),
                asBytes(tx, extraData),
                asArgWithTx(tx, value, (tx, val) => this.moduleManager.getUtils().createOptionSuiMoveCall(tx, val)),
            ],
        })
    }

    /**
     * Execute LayerZero compose operation (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param composeQueue - The compose queue object ID
     * @param from - Source address
     * @param guid - Globally unique identifier as bytes
     * @param index - Compose message index
     * @param message - Message payload as bytes
     * @param extraData - Additional execution data (optional)
     * @param value - Native token value to transfer
     * @returns Transaction result containing the execution call capability
     */
    executeLzComposeMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        composeQueue: string | TransactionArgument,
        from: string | TransactionArgument,
        guid: Uint8Array | TransactionArgument,
        index: number | TransactionArgument,
        message: Uint8Array | TransactionArgument,
        extraData: Uint8Array | TransactionArgument = new Uint8Array(),
        value = 0n
    ): TransactionResult {
        return tx.moveCall({
            target: this.#target('execute_lz_compose'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, adminCap),
                tx.object(this.objects.endpointV2),
                asObject(tx, composeQueue),
                asAddress(tx, from),
                asBytes32(tx, guid, this.moduleManager.getUtils()),
                asU16(tx, index),
                asBytes(tx, message),
                asBytes(tx, extraData),
                asArgWithTx(tx, value, (tx, val) => this.moduleManager.getUtils().createOptionSuiMoveCall(tx, val)),
            ],
        })
    }

    // Alert Functions

    /**
     * Record a failed lz_receive execution for off-chain processing (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param srcEid - Source endpoint ID
     * @param sender - Sender address as bytes
     * @param nonce - Message nonce
     * @param receiver - Receiver address
     * @param guid - Globally unique identifier as bytes
     * @param gas - Gas limit used for the execution attempt
     * @param value - Native token value included with the message
     * @param message - Message payload as bytes
     * @param extraData - Additional execution data
     * @param reason - Error message or failure reason
     */
    lzReceiveAlertMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        srcEid: number | TransactionArgument,
        sender: Uint8Array | TransactionArgument,
        nonce: bigint | number | string | TransactionArgument,
        receiver: string | TransactionArgument,
        guid: Uint8Array | TransactionArgument,
        gas: bigint | number | string | TransactionArgument,
        value: bigint | number | string | TransactionArgument,
        message: Uint8Array | TransactionArgument,
        extraData: Uint8Array | TransactionArgument,
        reason: string | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('lz_receive_alert'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, adminCap),
                asU32(tx, srcEid),
                asBytes32(tx, sender, this.moduleManager.getUtils()),
                asU64(tx, nonce),
                asAddress(tx, receiver),
                asBytes32(tx, guid, this.moduleManager.getUtils()),
                asU64(tx, gas),
                asU64(tx, value),
                asBytes(tx, message),
                asBytes(tx, extraData),
                asArgWithTx(tx, reason, (tx, val) => tx.pure.string(val)),
            ],
        })
    }

    /**
     * Record a failed lz_compose execution for off-chain processing (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param from - Source address
     * @param to - Destination address
     * @param guid - Globally unique identifier as bytes
     * @param index - Compose message index
     * @param gas - Gas limit used for the execution attempt
     * @param value - Native token value included with the compose
     * @param message - Compose message payload as bytes
     * @param extraData - Additional execution data
     * @param reason - Error message or failure reason
     */
    lzComposeAlertMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        from: string | TransactionArgument,
        to: string | TransactionArgument,
        guid: Uint8Array | TransactionArgument,
        index: number | TransactionArgument,
        gas: bigint | number | string | TransactionArgument,
        value: bigint | number | string | TransactionArgument,
        message: Uint8Array | TransactionArgument,
        extraData: Uint8Array | TransactionArgument,
        reason: string | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('lz_compose_alert'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, adminCap),
                asAddress(tx, from),
                asAddress(tx, to),
                asBytes32(tx, guid, this.moduleManager.getUtils()),
                asU16(tx, index),
                asU64(tx, gas),
                asU64(tx, value),
                asBytes(tx, message),
                asBytes(tx, extraData),
                asArgWithTx(tx, reason, (tx, val) => tx.pure.string(val)),
            ],
        })
    }

    // Native Drop Functions

    /**
     * Native drop function (admin only)
     * Takes a Coin<SUI> from caller and distributes it to recipients according to params
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param srcEid - Source endpoint ID
     * @param sender - Sender address as bytes
     * @param dstEid - Destination endpoint ID
     * @param oapp - OApp address
     * @param nonce - Message nonce
     * @param nativeDropParams - Array of native drop parameters
     * @param paymentCoin - Payment coin for the drop
     */
    nativeDropMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        srcEid: number | TransactionArgument,
        sender: Uint8Array | TransactionArgument,
        dstEid: number | TransactionArgument,
        oapp: string | TransactionArgument,
        nonce: bigint | number | string | TransactionArgument,
        nativeDropParams: NativeDropParams[],
        paymentCoin: TransactionArgument
    ): void {
        // Create individual NativeDropParams move calls for each parameter
        const dropParamCalls = nativeDropParams.map((param) =>
            tx.moveCall({
                target: this.#target('new_native_drop_params', 'native_drop_type'),
                arguments: [asAddress(tx, param.receiver), asU64(tx, param.amount)],
            })
        )

        tx.moveCall({
            target: this.#target('native_drop'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, adminCap),
                asU32(tx, srcEid),
                asBytes32(tx, sender, this.moduleManager.getUtils()),
                asU32(tx, dstEid),
                asAddress(tx, oapp),
                asU64(tx, nonce),
                tx.makeMoveVec({
                    type: `${this.packageId}::native_drop_type::NativeDropParams`,
                    elements: dropParamCalls,
                }),
                paymentCoin,
            ],
        })
    }

    // === Set Functions ===

    /**
     * Set default multiplier basis points for fee calculation (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param multiplierBps - The multiplier in basis points
     */
    setDefaultMultiplierBpsMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        multiplierBps: number | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_default_multiplier_bps'),
            arguments: [tx.object(this.objects.executor), asObject(tx, adminCap), asU16(tx, multiplierBps)],
        })
    }

    /**
     * Set deposit address for executor fees (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param depositAddress - The new deposit address
     */
    setDepositAddressMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        depositAddress: string | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_deposit_address'),
            arguments: [tx.object(this.objects.executor), asObject(tx, adminCap), asAddress(tx, depositAddress)],
        })
    }

    /**
     * Set destination configuration for executor (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param dstEid - Destination endpoint ID
     * @param config - Destination configuration parameters
     */
    setDstConfigMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        dstEid: number | TransactionArgument,
        config: DstConfig
    ): void {
        const configCall = tx.moveCall({
            target: this.#target('create_dst_config', 'executor_type'),
            arguments: [
                asU64(tx, config.lzReceiveBaseGas),
                asU64(tx, config.lzComposeBaseGas),
                asU16(tx, config.multiplierBps),
                asU128(tx, config.floorMarginUsd),
                asU128(tx, config.nativeCap),
            ],
        })
        tx.moveCall({
            target: this.#target('set_dst_config'),
            arguments: [tx.object(this.objects.executor), asObject(tx, adminCap), asU32(tx, dstEid), configCall],
        })
    }

    /**
     * Set price feed for executor (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param priceFeed - The price feed address
     */
    setPriceFeedMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        priceFeed: string | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_price_feed'),
            arguments: [tx.object(this.objects.executor), asObject(tx, adminCap), asAddress(tx, priceFeed)],
        })
    }

    /**
     * Set PTB builder move calls for executor worker operations (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param targetPtbBuilder - Target PTB builder address
     * @param getFeeMoveCalls - Get fee move calls transaction argument
     * @param assignJobMoveCalls - Assign job move calls transaction argument
     * @returns Transaction result containing the set PTB builder call
     */
    setPtbBuilderMoveCallsMoveCall(
        tx: Transaction,
        targetPtbBuilder: string | TransactionArgument,
        getFeeMoveCalls: TransactionArgument,
        assignJobMoveCalls: TransactionArgument
    ): TransactionResult {
        return tx.moveCall({
            target: this.#target('set_ptb_builder_move_calls'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, this.objects.executorOwnerCap),
                asAddress(tx, targetPtbBuilder),
                getFeeMoveCalls, // First element of Executor PTB result tuple
                assignJobMoveCalls, // Second element of Executor PTB result tuple
            ],
        })
    }

    /**
     * Set supported option types for a destination EID (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param dstEid - Destination endpoint ID
     * @param optionTypes - Array of supported option type values
     */
    setSupportedOptionTypesMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        dstEid: number | TransactionArgument,
        optionTypes: number[]
    ): void {
        tx.moveCall({
            target: this.#target('set_supported_option_types'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, adminCap),
                asU32(tx, dstEid),
                tx.pure(bcs.vector(bcs.u8()).serialize(optionTypes)),
            ],
        })
    }

    /**
     * Set worker fee library for executor (admin only)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID
     * @param workerFeeLib - The worker fee library address
     */
    setWorkerFeeLibMoveCall(
        tx: Transaction,
        adminCap: string | TransactionArgument,
        workerFeeLib: string | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_worker_fee_lib'),
            arguments: [tx.object(this.objects.executor), asObject(tx, adminCap), asAddress(tx, workerFeeLib)],
        })
    }

    /**
     * Set admin role (grant or revoke) (owner only)
     * @param tx - The transaction to add the move call to
     * @param ownerCap - The owner capability object ID
     * @param admin - The admin address
     * @param active - Whether to grant or revoke admin role
     */
    setAdminMoveCall(
        tx: Transaction,
        ownerCap: string | TransactionArgument,
        admin: string | TransactionArgument,
        active: boolean | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_admin'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, ownerCap),
                asAddress(tx, admin),
                asBool(tx, active),
            ],
        })
    }

    /**
     * Set supported message library (owner only)
     * @param tx - The transaction to add the move call to
     * @param ownerCap - The owner capability object ID
     * @param messageLib - The message library address
     * @param supported - Whether to support or remove support for the message library
     */
    setSupportedMessageLibMoveCall(
        tx: Transaction,
        ownerCap: string | TransactionArgument,
        messageLib: string | TransactionArgument,
        supported: boolean | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_supported_message_lib'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, ownerCap),
                asAddress(tx, messageLib),
                asBool(tx, supported),
            ],
        })
    }

    /**
     * Set allowlist status for an OApp sender (owner only)
     * @param tx - The transaction to add the move call to
     * @param ownerCap - The owner capability object ID
     * @param oapp - The OApp address
     * @param allowed - Whether to allow or remove from allowlist
     */
    setAllowlistMoveCall(
        tx: Transaction,
        ownerCap: string | TransactionArgument,
        oapp: string | TransactionArgument,
        allowed: boolean | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_allowlist'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, ownerCap),
                asAddress(tx, oapp),
                asBool(tx, allowed),
            ],
        })
    }

    /**
     * Set denylist status for an OApp sender (owner only)
     * @param tx - The transaction to add the move call to
     * @param ownerCap - The owner capability object ID
     * @param oapp - The OApp address
     * @param denied - Whether to deny or remove from denylist
     */
    setDenylistMoveCall(
        tx: Transaction,
        ownerCap: string | TransactionArgument,
        oapp: string | TransactionArgument,
        denied: boolean | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_denylist'),
            arguments: [
                tx.object(this.objects.executor),
                asObject(tx, ownerCap),
                asAddress(tx, oapp),
                asBool(tx, denied),
            ],
        })
    }

    /**
     * Set worker paused state (owner only)
     * @param tx - The transaction to add the move call to
     * @param ownerCap - The owner capability object ID
     * @param paused - Whether to pause or unpause the worker
     */
    setPausedMoveCall(
        tx: Transaction,
        ownerCap: string | TransactionArgument,
        paused: boolean | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_paused'),
            arguments: [tx.object(this.objects.executor), asObject(tx, ownerCap), asBool(tx, paused)],
        })
    }

    // === View Functions ===

    /**
     * Get the size of the allowlist
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the allowlist size
     */
    allowlistSizeMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('allowlist_size'),
            arguments: [tx.object(this.objects.executor)],
        })
    }

    /**
     * Get the size of the allowlist
     * @returns Promise<bigint> - The number of addresses in the allowlist
     */
    async allowlistSize(): Promise<bigint> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.allowlistSizeMoveCall(tx)
            },
            (result) => BigInt(bcs.U64.parse(result[0].value))
        )
    }

    /**
     * Get default multiplier basis points
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the default multiplier bps
     */
    defaultMultiplierBpsMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('default_multiplier_bps'),
            arguments: [tx.object(this.objects.executor)],
        })
    }

    /**
     * Get default multiplier basis points
     * @returns Promise<number> - The default multiplier in basis points
     */
    async defaultMultiplierBps(): Promise<number> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.defaultMultiplierBpsMoveCall(tx)
            },
            (result) => bcs.U16.parse(result[0].value)
        )
    }

    /**
     * Get executor deposit address for fee collection
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the deposit address
     */
    depositAddressMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('deposit_address'),
            arguments: [tx.object(this.objects.executor)],
        })
    }

    /**
     * Get executor deposit address for fee collection
     * @returns Promise<string> - The deposit address
     */
    async depositAddress(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.depositAddressMoveCall(tx)
            },
            (result) => bcs.Address.parse(result[0].value)
        )
    }

    /**
     * Get destination configuration for executor
     * @param tx - The transaction to add the move call to
     * @param dstEid - Destination endpoint ID
     * @returns Transaction result containing the destination configuration
     */
    dstConfigMoveCall(tx: Transaction, dstEid: number | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('dst_config'),
            arguments: [tx.object(this.objects.executor), asU32(tx, dstEid)],
        })
    }

    /**
     * Get destination configuration for executor
     * @param dstEid - Destination endpoint ID
     * @returns Promise<DstConfig> - The destination configuration
     */
    async dstConfig(dstEid: number): Promise<DstConfig> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.dstConfigMoveCall(tx, dstEid)
            },
            (result) => this.parseDstConfig(result[0].value)
        )
    }

    /**
     * Check if an address has ACL (Access Control List) permission
     * @param tx - The transaction to add the move call to
     * @param account - The account address to check
     * @returns Transaction result containing the ACL permission status
     */
    hasAclMoveCall(tx: Transaction, account: string): TransactionResult {
        return tx.moveCall({
            target: this.#target('has_acl'),
            arguments: [tx.object(this.objects.executor), asAddress(tx, account)],
        })
    }

    /**
     * Check if an address has ACL (Access Control List) permission
     * @param account - The account address to check
     * @returns Promise<boolean> - True if the address has ACL permission
     */
    async hasAcl(account: string): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.hasAclMoveCall(tx, account)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Get all registered executor admins
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing array of admin addresses
     */
    adminsMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('admins'),
            arguments: [tx.object(this.objects.executor)],
        })
    }

    /**
     * Get all registered executor admins
     * @returns Promise<string[]> - Array of admin addresses
     */
    async admins(): Promise<string[]> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.adminsMoveCall(tx)
            },
            (result) => {
                const parsed = bcs.vector(bcs.Address).parse(result[0].value)
                return parsed
            }
        )
    }

    /**
     * Check if an admin cap is valid (sync Move call)
     * @param tx - The transaction to add the move call to
     * @param adminCap - The admin capability object ID to check
     * @returns TransactionResult - Result containing admin status
     */
    isAdminMoveCall(tx: Transaction, adminCap: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('is_admin'),
            arguments: [tx.object(this.objects.executor), asObject(tx, adminCap)],
        })
    }

    /**
     * Check if an admin cap is valid (async simulation)
     * @param adminCap - The admin capability object ID to check
     * @returns Promise<boolean> - True if the admin cap is valid
     */
    async isAdmin(adminCap: string): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.isAdminMoveCall(tx, adminCap)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Check if an address is an admin
     * @param tx - The transaction to add the move call to
     * @param admin - The admin address to check
     * @returns Transaction result containing the admin status
     */
    isAdminAddressMoveCall(tx: Transaction, admin: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('is_admin_address'),
            arguments: [tx.object(this.objects.executor), asAddress(tx, admin)],
        })
    }

    /**
     * Check if an address is an admin
     * @param admin - The admin address to check
     * @returns Promise<boolean> - True if the address is an admin
     */
    async isAdminAddress(admin: string): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.isAdminAddressMoveCall(tx, admin)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Check if a message library is supported by this executor
     * @param tx - The transaction to add the move call to
     * @param messageLib - The message library address to check
     * @returns Transaction result containing the support status
     */
    isSupportedMessageLibMoveCall(tx: Transaction, messageLib: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('is_supported_message_lib'),
            arguments: [tx.object(this.objects.executor), asAddress(tx, messageLib)],
        })
    }

    /**
     * Check if a message library is supported by this executor
     * @param messageLib - The message library address to check
     * @returns Promise<boolean> - True if the message library is supported
     */
    async isSupportedMessageLib(messageLib: string): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.isSupportedMessageLibMoveCall(tx, messageLib)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Check if an address is allowlisted
     * @param tx - The transaction to add the move call to
     * @param account - The account address to check
     * @returns Transaction result containing the allowlist status
     */
    isAllowlistedMoveCall(tx: Transaction, account: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('is_allowlisted'),
            arguments: [tx.object(this.objects.executor), asAddress(tx, account)],
        })
    }

    /**
     * Check if an address is in the allowlist
     * @param account - The account address to check
     * @returns Promise<boolean> - True if the address is allowlisted
     */
    async isAllowlisted(account: string): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.isAllowlistedMoveCall(tx, account)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Check if an address is denylisted
     * @param tx - The transaction to add the move call to
     * @param account - The account address to check
     * @returns Transaction result containing the denylist status
     */
    isDenylistedMoveCall(tx: Transaction, account: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('is_denylisted'),
            arguments: [tx.object(this.objects.executor), asAddress(tx, account)],
        })
    }

    /**
     * Check if an address is in the denylist
     * @param account - The account address to check
     * @returns Promise<boolean> - True if the address is denylisted
     */
    async isDenylisted(account: string): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.isDenylistedMoveCall(tx, account)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Check if executor worker is paused
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the paused status
     */
    isPausedMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('is_paused'),
            arguments: [tx.object(this.objects.executor)],
        })
    }

    /**
     * Check if executor worker is paused
     * @returns Promise<boolean> - True if the worker is paused
     */
    async isPaused(): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.isPausedMoveCall(tx)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Get executor price feed address
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the price feed address
     */
    priceFeedMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('price_feed'),
            arguments: [tx.object(this.objects.executor)],
        })
    }

    /**
     * Get executor price feed address
     * @returns Promise<string> - The price feed address
     */
    async priceFeed(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.priceFeedMoveCall(tx)
            },
            (result) => bcs.Address.parse(result[0].value)
        )
    }

    /**
     * Get supported option types for a destination EID
     * @param tx - The transaction to add the move call to
     * @param dstEid - Destination endpoint ID
     * @returns Transaction result containing supported option types
     */
    supportedOptionTypesMoveCall(tx: Transaction, dstEid: number | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('supported_option_types'),
            arguments: [tx.object(this.objects.executor), asU32(tx, dstEid)],
        })
    }

    /**
     * Get supported option types for a destination EID
     * @param dstEid - Destination endpoint ID
     * @returns Promise<number[]> - Array of supported option types as bytes
     */
    async supportedOptionTypes(dstEid: number): Promise<number[]> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.supportedOptionTypesMoveCall(tx, dstEid)
            },
            (result) => {
                const parsed = bcs.vector(bcs.u8()).parse(result[0].value)
                return Array.from(parsed)
            }
        )
    }

    /**
     * Get executor worker capability address
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the worker capability address
     */
    workerCapAddressMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('worker_cap_address'),
            arguments: [tx.object(this.objects.executor)],
        })
    }

    /**
     * Get executor worker capability address
     * @returns Promise<string> - The worker capability address
     */
    async workerCapAddress(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.workerCapAddressMoveCall(tx)
            },
            (result) => bcs.Address.parse(result[0].value)
        )
    }

    /**
     * Get executor worker fee library address
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the worker fee library address
     */
    workerFeeLibMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('worker_fee_lib'),
            arguments: [tx.object(this.objects.executor)],
        })
    }

    /**
     * Get executor worker fee library address
     * @returns Promise<string> - The worker fee library address
     */
    async workerFeeLib(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.workerFeeLibMoveCall(tx)
            },
            (result) => bcs.Address.parse(result[0].value)
        )
    }

    /**
     * Get admin capability ID from admin address
     * @param tx - The transaction to add the move call to
     * @param admin - The admin address
     * @returns Transaction result containing the admin capability ID
     */
    adminCapIdMoveCall(tx: Transaction, admin: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('admin_cap_id'),
            arguments: [tx.object(this.objects.executor), asAddress(tx, admin)],
        })
    }

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

    /**
     * Get Executor object address from worker registry using this Executor's worker capability (as a move call)
     * This function chains Move calls to decode worker info and extract the Executor object address
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the Executor object address
     */
    getExecutorObjectAddressMoveCall(tx: Transaction): TransactionResult {
        // Step 1: Get this Executor's worker capability address
        const workerCapAddress = this.workerCapAddressMoveCall(tx)

        // Step 2: Get worker info bytes from registry
        const workerInfoBytes = this.moduleManager
            .getWorkerRegistry(this.client)
            .getWorkerInfoMoveCall(tx, workerCapAddress)

        // Step 3: Decode worker info using worker_common::worker_info_v1::decode
        const workerInfo = tx.moveCall({
            target: `${this.moduleManager.packages.workerCommon}::worker_info_v1::decode`,
            arguments: [workerInfoBytes],
        })

        // Step 4: Extract worker_info field from decoded WorkerInfoV1
        const executorInfoBytes = tx.moveCall({
            target: `${this.moduleManager.packages.workerCommon}::worker_info_v1::worker_info`,
            arguments: [workerInfo],
        })

        // Step 5: Decode Executor info using executor::executor_info_v1::decode
        const executorInfo = tx.moveCall({
            target: `${this.packageId}::executor_info_v1::decode`,
            arguments: [executorInfoBytes],
        })

        // Step 6: Extract executor_object address from decoded ExecutorInfoV1
        return tx.moveCall({
            target: `${this.packageId}::executor_info_v1::executor_object`,
            arguments: [executorInfo],
        })
    }

    /**
     * Get Executor object address from worker registry using this Executor's worker capability
     * This function uses Move calls to decode worker info and extract the Executor object address
     * @returns Promise<string> - The Executor object address
     * @throws Will throw an error if worker info is not found or if decoding fails
     */
    async getExecutorObjectAddress(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.getExecutorObjectAddressMoveCall(tx)
            },
            (result) => {
                // The result is the Executor object address directly from the Move call chain
                return bcs.Address.parse(result[0].value)
            }
        )
    }

    // === Private Helper Functions ===

    private parseDstConfig(data: Uint8Array): DstConfig {
        const config = DstConfigBcs.parse(data)
        return {
            lzReceiveBaseGas: BigInt(config.lz_receive_base_gas),
            lzComposeBaseGas: BigInt(config.lz_compose_base_gas),
            multiplierBps: config.multiplier_bps,
            floorMarginUsd: BigInt(config.floor_margin_usd),
            nativeCap: BigInt(config.native_cap),
        }
    }

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