import sha3 from 'js-sha3'

import { ModuleManager } from '../module-manager'
import { CallTypeName, LzTypeName, Modules } from '../types'

// Predefined type names
const TYPE_CONFIG = {
    [LzTypeName.EndpointQuoteParam]: { pkg: Modules.Endpoint, module: 'endpoint_quote', type: 'QuoteParam' },
    [LzTypeName.EndpointSendParam]: { pkg: Modules.Endpoint, module: 'endpoint_send', type: 'SendParam' },
    [LzTypeName.MessageLibQuoteParam]: { pkg: Modules.Endpoint, module: 'message_lib_quote', type: 'QuoteParam' },
    [LzTypeName.MessageLibSendParam]: { pkg: Modules.Endpoint, module: 'message_lib_send', type: 'SendParam' },
    [LzTypeName.MessageLibSendResult]: { pkg: Modules.Endpoint, module: 'message_lib_send', type: 'SendResult' },
    [LzTypeName.MessageLibSetConfigParam]: {
        pkg: Modules.Endpoint,
        module: 'message_lib_set_config',
        type: 'SetConfigParam',
    },
    [LzTypeName.MessagingFee]: { pkg: Modules.Endpoint, module: 'messaging_fee', type: 'MessagingFee' },
    [LzTypeName.MessagingReceipt]: { pkg: Modules.Endpoint, module: 'messaging_receipt', type: 'MessagingReceipt' },
    [LzTypeName.EndpointLzReceiveParam]: { pkg: Modules.Endpoint, module: 'lz_receive', type: 'LzReceiveParam' },
    [LzTypeName.EndpointLzComposeParam]: { pkg: Modules.Endpoint, module: 'lz_compose', type: 'LzComposeParam' },
    [LzTypeName.MoveCall]: { pkg: Modules.Call, module: 'move_call', type: 'MoveCall' },
    [LzTypeName.CallVoid]: { pkg: Modules.Call, module: 'call', type: 'Void' },
} as const

// Call type configuration
const CALL_TYPE_CONFIG = {
    [CallTypeName.EndpointQuoteCall]: { param: LzTypeName.EndpointQuoteParam, result: LzTypeName.MessagingFee },
    [CallTypeName.EndpointSendCall]: { param: LzTypeName.EndpointSendParam, result: LzTypeName.MessagingReceipt },
    [CallTypeName.MessageLibQuoteCall]: { param: LzTypeName.MessageLibQuoteParam, result: LzTypeName.MessagingFee },
    [CallTypeName.MessageLibSendCall]: {
        param: LzTypeName.MessageLibSendParam,
        result: LzTypeName.MessageLibSendResult,
    },
    [CallTypeName.MessageLibSetConfigCall]: { param: LzTypeName.MessageLibSetConfigParam, result: LzTypeName.CallVoid },
    [CallTypeName.EndpointLzReceiveCall]: { param: LzTypeName.EndpointLzReceiveParam, result: LzTypeName.CallVoid },
    [CallTypeName.EndpointLzComposeCall]: { param: LzTypeName.EndpointLzComposeParam, result: LzTypeName.CallVoid },
} as const

export function getTypeName(moduleManager: ModuleManager, type: LzTypeName | CallTypeName): string {
    // Handle simple types
    if (type in TYPE_CONFIG) {
        const config = TYPE_CONFIG[type as keyof typeof TYPE_CONFIG]
        const { packageId } = moduleManager.getModule<{ packageId: string }>(config.pkg)
        return buildTypeName(packageId, config.module, config.type)
    }

    // Handle Call types
    if (type in CALL_TYPE_CONFIG) {
        const callConfig = CALL_TYPE_CONFIG[type as keyof typeof CALL_TYPE_CONFIG]
        const paramType = getTypeName(moduleManager, callConfig.param)
        const resultType = getTypeName(moduleManager, callConfig.result)
        return buildCallTypeName(moduleManager.getCall().packageId, paramType, resultType)
    }

    throw new Error(`Unknown type name: ${JSON.stringify(type)}`)
}

/**
 * Normalizes a Sui package ID with configurable formatting options
 * @param packageId - The package ID to normalize (with or without 0x prefix)
 * @param includePrefix - Whether to include the '0x' prefix in the result (default: false)
 * @param trimLeadingZeros - Whether to remove leading zeros (default: true)
 * @returns The normalized package ID
 *
 * @example
 * normalizeSuiPackageId('0x0001abc', false, true) // '1abc'
 * normalizeSuiPackageId('0x0001abc', true, true) // '0x1abc'
 * normalizeSuiPackageId('0x0001abc', true, false) // '0x0001abc'
 */
export function normalizeSuiPackageId(packageId: string, includePrefix = false, trimLeadingZeros = false): string {
    // Remove 0x prefix if present
    let normalized = packageId.startsWith('0x') ? packageId.slice(2) : packageId

    // Trim leading zeros if requested
    if (trimLeadingZeros) {
        // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
        normalized = normalized.replace(/^0+/, '') || '0'
    }

    // Add prefix back if requested
    return includePrefix ? `0x${normalized}` : normalized
}

export function callId(moduleManager: ModuleManager, type: CallTypeName): string {
    return sha3.keccak_256(Buffer.from(getTypeName(moduleManager, type)))
}

// === Internal Functions ===

// Helper function: build basic type name
function buildTypeName(packageId: string, module: string, type: string): string {
    return `${normalizeSuiPackageId(packageId)}::${module}::${type}`
}

// Helper function: build Call type name
function buildCallTypeName(packageId: string, paramType: string, resultType: string): string {
    return `${normalizeSuiPackageId(packageId)}::call::Call<${paramType},${resultType}>`
}
