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

import { ModuleManager } from '../../module-manager'
import { ObjectOptions } from '../../types'
import { asAddress, executeSimulate } from '../../utils'

const MODULE_NAME = 'uln_302_ptb_builder'

export const Uln302PtbBuilderErrorCode = {
    // ULN302 PTB Builder related errors (matching uln_302_ptb_builder.move)
    Uln302PtbBuilder_EWorkerPtbsNotFound: 1,
} as const

export class Uln302PtbBuilder {
    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
    }

    // === Set Functions ===

    /**
     * Set worker PTBs for ULN302 PTB builder
     * @param tx - The transaction to add the move call to
     * @param call - Call object from worker's set_worker_ptbs or transaction argument
     */
    setWorkerPtbsMoveCall(tx: Transaction, call: TransactionArgument): void {
        tx.moveCall({
            target: this.#target('set_worker_ptbs'),
            arguments: [tx.object(this.objects.uln302PtbBuilder), call],
        })
    }

    // === View Functions ===

    /**
     * Creates a transaction to get PTB builder info
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing PTB builder information
     */
    getPtbBuilderInfoMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('get_ptb_builder_info'),
            arguments: [
                tx.object(this.objects.uln302PtbBuilder),
                tx.object(this.objects.uln302),
                tx.object(this.objects.treasury),
                tx.object(this.objects.endpointV2),
            ],
        })
    }

    /**
     * Check if worker PTBs are set for a specific worker capability
     * @param tx - The transaction to add the move call to
     * @param workerCap - Worker capability address to check or transaction argument
     * @returns Transaction result containing boolean indicating if PTBs are set
     */
    isWorkerPtbsSetMoveCall(tx: Transaction, workerCap: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('is_worker_ptbs_set'),
            arguments: [tx.object(this.objects.uln302PtbBuilder), asAddress(tx, workerCap)],
        })
    }

    /**
     * Check if worker PTBs are set for a specific worker capability
     * @param workerCap - Worker capability address to check
     * @returns Promise<boolean> - True if worker PTBs are set
     */
    async isWorkerPtbsSet(workerCap: string): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.isWorkerPtbsSetMoveCall(tx, workerCap)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * Get worker PTBs for a specific worker capability
     * @param tx - The transaction to add the move call to
     * @param workerCap - Worker capability address or transaction argument
     * @returns Transaction result containing the worker PTBs
     */
    getWorkerPtbsMoveCall(tx: Transaction, workerCap: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('get_worker_ptbs'),
            arguments: [tx.object(this.objects.uln302PtbBuilder), asAddress(tx, workerCap)],
        })
    }

    // === Call ID Functions ===

    /**
     * Get the call ID for executor get fee operations
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the executor get fee call ID
     */
    executorGetFeeCallIdMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('executor_get_fee_call_id'),
            arguments: [],
        })
    }

    /**
     * Get the executor get fee call ID as a hex string
     * @returns Promise<string> - The executor get fee call ID in hex format
     */
    async executorGetFeeCallId(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.executorGetFeeCallIdMoveCall(tx)
            },
            (result) => Buffer.from(bcs.vector(bcs.u8()).parse(result[0].value)).toString('hex')
        )
    }

    /**
     * Get the call ID for DVN get fee multi-call operations
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the DVN get fee multi-call ID
     */
    dvnGetFeeMultiCallIdMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('dvn_get_fee_multi_call_id'),
            arguments: [],
        })
    }

    /**
     * Get the DVN get fee multi-call ID as a hex string
     * @returns Promise<string> - The DVN get fee multi-call ID in hex format
     */
    async dvnGetFeeMultiCallId(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.dvnGetFeeMultiCallIdMoveCall(tx)
            },
            (result) => Buffer.from(bcs.vector(bcs.u8()).parse(result[0].value)).toString('hex')
        )
    }

    /**
     * Get the call ID for executor assign job operations
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the executor assign job call ID
     */
    executorAssignJobCallIdMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('executor_assign_job_call_id'),
            arguments: [],
        })
    }

    /**
     * Get the executor assign job call ID as a hex string
     * @returns Promise<string> - The executor assign job call ID in hex format
     */
    async executorAssignJobCallId(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.executorAssignJobCallIdMoveCall(tx)
            },
            (result) => Buffer.from(bcs.vector(bcs.u8()).parse(result[0].value)).toString('hex')
        )
    }

    /**
     * Get the call ID for DVN assign job multi-call operations
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the DVN assign job multi-call ID
     */
    dvnAssignJobMultiCallIdMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('dvn_assign_job_multi_call_id'),
            arguments: [],
        })
    }

    /**
     * Get the DVN assign job multi-call ID as a hex string
     * @returns Promise<string> - The DVN assign job multi-call ID in hex format
     */
    async dvnAssignJobMultiCallId(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.dvnAssignJobMultiCallIdMoveCall(tx)
            },
            (result) => Buffer.from(bcs.vector(bcs.u8()).parse(result[0].value)).toString('hex')
        )
    }

    /**
     * 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}`
    }
}
