import { SuiClient, SuiObjectResponse } from '@mysten/sui/client'

import { MAX_BATCH_SIZE, MoveCall } from '../types'

/**
 * Represents a move call with its associated object details
 */
export interface MoveCallWithObjectDetails {
    moveCall: MoveCall
    objectDetails: Map<string, SuiObjectResponse>
}

/**
 * MoveCallObjectFetcher fetches object details for move calls once
 * and provides them to validators to avoid multiple API calls
 */
export class MoveCallObjectFetcher {
    private client: SuiClient
    private batchSize: number

    constructor(client: SuiClient, batchSize: number = MAX_BATCH_SIZE) {
        this.client = client
        this.batchSize = batchSize
    }

    /**
     * Fetches object details for all objects referenced in move calls
     * @param moveCalls - Array of move calls to fetch object details for
     * @param options - Options for what object data to fetch
     * @returns Array of move calls with their associated object details
     */
    async fetchObjectDetails(
        moveCalls: MoveCall[],
        options: {
            showOwner?: boolean
            showType?: boolean
            showContent?: boolean
            showBcs?: boolean
            showDisplay?: boolean
            showStorageRebate?: boolean
        } = {}
    ): Promise<MoveCallWithObjectDetails[]> {
        // Extract all unique object IDs from move calls
        const objectIdSet = new Set<string>()
        for (const moveCall of moveCalls) {
            for (const arg of moveCall.arguments) {
                if ('Object' in arg) {
                    objectIdSet.add(arg.Object)
                }
            }
        }

        const objectIds = Array.from(objectIdSet)
        if (objectIds.length === 0) {
            // No objects to fetch, return move calls with empty object details
            return moveCalls.map((moveCall) => ({
                moveCall,
                objectDetails: new Map<string, SuiObjectResponse>(),
            }))
        }

        // Validate batch size
        if (this.batchSize > MAX_BATCH_SIZE || this.batchSize < 1) {
            throw new Error(`batchSize must be between 1 and ${MAX_BATCH_SIZE}`)
        }

        // Fetch all objects in batches
        const objectDetailsMap = new Map<string, SuiObjectResponse>()

        for (let i = 0; i < objectIds.length; i += this.batchSize) {
            const batch = objectIds.slice(i, i + this.batchSize)
            const objects = await this.client.multiGetObjects({
                ids: batch,
                options,
            })

            // Map object responses to their IDs
            objects.forEach((object, index) => {
                const objectId = batch[index]
                objectDetailsMap.set(objectId, object)
            })
        }

        // Create move calls with object details
        return moveCalls.map((moveCall) => {
            // Get object details for this specific move call
            const moveCallObjectDetails = new Map<string, SuiObjectResponse>()

            for (const arg of moveCall.arguments) {
                if ('Object' in arg) {
                    const objectId = arg.Object
                    const objectDetail = objectDetailsMap.get(objectId)
                    if (objectDetail) {
                        moveCallObjectDetails.set(objectId, objectDetail)
                    }
                }
            }

            return {
                moveCall,
                objectDetails: moveCallObjectDetails,
            }
        })
    }
}
