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

import { Utils } from '../modules'

/**
 * Type guard to check if a value is already a TransactionArgument
 * TransactionArgument can be:
 * 1. Argument object with $kind property
 * 2. Synchronous function: (tx: Transaction) => Argument
 * 3. Asynchronous function: AsyncTransactionThunk
 */
export function isTransactionArgument(value: unknown): value is TransactionArgument {
    // Check if it's a function (sync or async)
    if (typeof value === 'function') {
        return true
    }

    // Check if it's an Argument object
    if (value !== null && typeof value === 'object' && '$kind' in value) {
        const obj = value as { $kind: string }
        return (
            obj.$kind === 'GasCoin' || obj.$kind === 'Input' || obj.$kind === 'Result' || obj.$kind === 'NestedResult'
        )
    }

    return false
}

/**
 * Convert value to TransactionArgument using a simple converter function
 * @param value - The value to convert (primitive type or TransactionArgument)
 * @param converter - Function to convert primitive type to TransactionArgument
 * @returns TransactionArgument
 */
export function asArg<T>(
    value: T | TransactionArgument,
    converter: (val: T) => TransactionArgument
): TransactionArgument {
    if (isTransactionArgument(value)) {
        return value
    }
    return converter(value)
}

/**
 * Convert value to TransactionArgument using a converter that requires Transaction
 * @param tx - The transaction
 * @param value - The value to convert
 * @param converter - Function to convert value using transaction
 * @returns TransactionArgument
 */
export function asArgWithTx<T>(
    tx: Transaction,
    value: T | TransactionArgument,
    converter: (tx: Transaction, val: T) => TransactionArgument
): TransactionArgument {
    if (isTransactionArgument(value)) {
        return value
    }
    return converter(tx, value)
}

/**
 * Async version: Convert value to TransactionArgument using an async converter that requires Transaction
 * @param tx - The transaction
 * @param value - The value to convert
 * @param converter - Async function to convert value using transaction
 * @returns Promise<TransactionArgument>
 */
export async function asArgWithTxAsync<T>(
    tx: Transaction,
    value: T | TransactionArgument,
    converter: (tx: Transaction, val: T) => Promise<TransactionArgument>
): Promise<TransactionArgument> {
    if (isTransactionArgument(value)) {
        return value
    }
    return converter(tx, value)
}

// ===== Simplified conversion methods =====

/**
 * Convert to object reference TransactionArgument
 */
export function asObject(tx: Transaction, value: string | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.object(val))
}

/**
 * Convert to address TransactionArgument
 */
export function asAddress(tx: Transaction, value: string | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.address(val))
}

/**
 * Convert to u8 TransactionArgument
 */
export function asU8(tx: Transaction, value: number | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.u8(val))
}

/**
 * Convert to u16 TransactionArgument
 */
export function asU16(tx: Transaction, value: number | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.u16(val))
}

/**
 * Convert to u32 TransactionArgument
 */
export function asU32(tx: Transaction, value: number | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.u32(val))
}

/**
 * Convert to u64 TransactionArgument (supports bigint, number, or string)
 */
export function asU64(tx: Transaction, value: bigint | number | string | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.u64(val))
}

/**
 * Convert to u128 TransactionArgument (supports bigint, number, or string)
 */
export function asU128(tx: Transaction, value: bigint | number | string | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.u128(val))
}

/**
 * Convert to u256 TransactionArgument (supports bigint, number, or string)
 */
export function asU256(tx: Transaction, value: bigint | number | string | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.u256(val))
}

/**
 * Convert Uint8Array to Bytes32 using moduleManager's fromBytesMoveCall
 * @param tx - The transaction
 * @param value - Uint8Array or TransactionArgument
 * @param utils - utils Module
 * @returns TransactionArgument
 */
export function asBytes32(tx: Transaction, value: Uint8Array | TransactionArgument, utils: Utils): TransactionArgument {
    return asArgWithTx(tx, value, (tx, bytes) => utils.fromBytesMoveCall(tx, bytes))
}

/**
 * Convert Uint8Array to serialized bytes vector TransactionArgument
 * @param tx - The transaction
 * @param value - Uint8Array or TransactionArgument
 * @returns TransactionArgument
 */
export function asBytes(tx: Transaction, value: Uint8Array | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure(bcs.vector(bcs.u8()).serialize(val)))
}
/**
 * Convert Uint8Array Vector to serialized bytes vector TransactionArgument
 * @param tx - The transaction
 * @param value - Uint8Array or TransactionArgument
 * @returns TransactionArgument
 */
export function asBytesVector(tx: Transaction, value: Uint8Array[] | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure(bcs.vector(bcs.vector(bcs.u8())).serialize(val)))
}

/**
 * Convert string to string TransactionArgument
 * @param tx - The transaction
 * @param value - string or TransactionArgument
 * @returns TransactionArgument
 */
export function asString(tx: Transaction, value: string | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.string(val))
}

/**
 * Convert address array to serialized vector<address> TransactionArgument
 * @param tx - The transaction
 * @param value - string[] or TransactionArgument
 * @returns TransactionArgument
 */
export function asAddressVector(tx: Transaction, value: string[] | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure(bcs.vector(bcs.Address).serialize(val)))
}

/**
 * Convert boolean to boolean TransactionArgument
 * @param tx - The transaction
 * @param value - boolean or TransactionArgument
 * @returns TransactionArgument
 */
export function asBool(tx: Transaction, value: boolean | TransactionArgument): TransactionArgument {
    return asArg(value, (val) => tx.pure.bool(val))
}
