// file: kb_lib/src/dex/meteora_dlmm.rs

//! Meteora DLMM transaction decoder.
//!
//! This first decoder version is intentionally conservative. It only emits
//! decoded events when the projected instruction or transaction logs expose
//! clear DLMM create/swap hints.

const DLMM_DISCRIMINATOR_CLAIM_FEE2: [u8; 8] = [0x70, 0xbf, 0x65, 0xab, 0x1c, 0x90, 0x7f, 0xbb];

const DLMM_DISCRIMINATOR_INITIALIZE_BIN_ARRAY: [u8; 8] =
    [0x23, 0x56, 0x13, 0xb9, 0x4e, 0xd4, 0x4b, 0xd3];

const DLMM_DISCRIMINATOR_INITIALIZE_POSITION: [u8; 8] =
    [0xdb, 0xc0, 0xea, 0x47, 0xbe, 0xbf, 0x66, 0x50];

const DLMM_DISCRIMINATOR_ADD_LIQUIDITY: [u8; 8] = [0xb5, 0x9d, 0x59, 0x43, 0x8f, 0xb6, 0x34, 0x48];

const DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY: [u8; 8] =
    [0x50, 0x55, 0xd1, 0x48, 0x18, 0xce, 0xb1, 0x6c];

const DLMM_DISCRIMINATOR_INITIALIZE_LB_PAIR: [u8; 8] =
    [0x2d, 0x9a, 0xed, 0xd2, 0xdd, 0x0f, 0xa6, 0x5c];

const DLMM_DISCRIMINATOR_INITIALIZE_LB_PAIR2: [u8; 8] =
    [0x49, 0x3b, 0x24, 0x78, 0xed, 0x53, 0x6c, 0xc6];

const DLMM_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_LB_PAIR: [u8; 8] =
    [0x2e, 0x27, 0x29, 0x87, 0x6f, 0xb7, 0xc8, 0x40];

const DLMM_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_LB_PAIR2: [u8; 8] =
    [0xf3, 0x49, 0x81, 0x7e, 0x33, 0x13, 0xf1, 0x6b];

const DLMM_DISCRIMINATOR_SWAP: [u8; 8] = [0xf8, 0xc6, 0x9e, 0x91, 0xe1, 0x75, 0x87, 0xc8];

const DLMM_DISCRIMINATOR_SWAP2: [u8; 8] = [0x41, 0x4b, 0x3f, 0x4c, 0xeb, 0x5b, 0x5b, 0x88];

const DLMM_DISCRIMINATOR_SWAP_EXACT_OUT: [u8; 8] = [0xfa, 0x49, 0x65, 0x21, 0x26, 0xcf, 0x4b, 0xb8];

const DLMM_DISCRIMINATOR_SWAP_EXACT_OUT2: [u8; 8] =
    [0x2b, 0xd7, 0xf7, 0x84, 0x89, 0x3c, 0xf3, 0x51];

const DLMM_DISCRIMINATOR_SWAP_WITH_PRICE_IMPACT: [u8; 8] =
    [0x38, 0xad, 0xe6, 0xd0, 0xad, 0xe4, 0x9c, 0xcd];

/// Decoded Meteora DLMM create-pool event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmCreatePoolDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token X/base mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token Y/quote mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional preset/config account.
    pub config_account: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM swap event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmSwapDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Trade side relative to normalized base, when inferable.
    pub trade_side: crate::SwapTradeSide,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token X/base mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token Y/quote mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional reserve X token account.
    pub reserve_x_account: std::option::Option<std::string::String>,
    /// Optional reserve Y token account.
    pub reserve_y_account: std::option::Option<std::string::String>,
    /// Optional user token-in account.
    pub user_token_in_account: std::option::Option<std::string::String>,
    /// Optional user token-out account.
    pub user_token_out_account: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM liquidity lifecycle event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmLiquidityDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Normalized decoded event kind.
    pub event_kind: std::string::String,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token X/base mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token Y/quote mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Optional actor wallet or owner account.
    pub actor_wallet: std::option::Option<std::string::String>,
    /// Optional decoded base/token-X amount.
    pub base_amount_raw: std::option::Option<std::string::String>,
    /// Optional decoded quote/token-Y amount.
    pub quote_amount_raw: std::option::Option<std::string::String>,
    /// Optional decoded liquidity amount.
    pub liquidity_amount_raw: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM pool lifecycle event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MeteoraDlmmPoolLifecycleDecoded {
    /// Parent transaction id.
    pub transaction_id: i64,
    /// Parent instruction id.
    pub instruction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Program id.
    pub program_id: std::string::String,
    /// Normalized decoded event kind.
    pub event_kind: std::string::String,
    /// Optional DLMM pair/pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token X/base mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token Y/quote mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Decoded payload.
    pub payload_json: serde_json::Value,
}

/// Decoded Meteora DLMM event.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum MeteoraDlmmDecodedEvent {
    /// DLMM pair/pool creation.
    CreatePool(MeteoraDlmmCreatePoolDecoded),
    /// DLMM swap.
    Swap(MeteoraDlmmSwapDecoded),
    /// DLMM liquidity lifecycle event.
    Liquidity(MeteoraDlmmLiquidityDecoded),
    /// DLMM pool lifecycle event that is not the canonical create-pool event.
    PoolLifecycle(MeteoraDlmmPoolLifecycleDecoded),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MeteoraDlmmInstructionKind {
    CreatePool,
    Swap,
    LiquidityAdd,
    LiquidityRemove,
    PositionOpen,
    PoolLifecycle,
    Ignore,
    Unknown,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MeteoraDlmmInstructionName {
    InitializeLbPair,
    InitializeLbPair2,
    InitializeCustomizablePermissionlessLbPair,
    InitializeCustomizablePermissionlessLbPair2,
    Swap,
    Swap2,
    SwapExactOut,
    SwapExactOut2,
    SwapWithPriceImpact,
    InitializeBinArray,
    AddLiquidity,
    RemoveLiquidity,
    ClaimFee2,
    InitializePosition,
    Unknown,
}

impl MeteoraDlmmInstructionName {
    fn as_str(&self) -> &'static str {
        match self {
            Self::InitializeLbPair => return "initialize_lb_pair",
            Self::InitializeLbPair2 => return "initialize_lb_pair2",
            Self::InitializeCustomizablePermissionlessLbPair => {
                return "initialize_customizable_permissionless_lb_pair";
            },
            Self::InitializeCustomizablePermissionlessLbPair2 => {
                return "initialize_customizable_permissionless_lb_pair2";
            },
            Self::Swap => return "swap",
            Self::Swap2 => return "swap2",
            Self::SwapExactOut => return "swap_exact_out",
            Self::SwapExactOut2 => return "swap_exact_out2",
            Self::SwapWithPriceImpact => return "swap_with_price_impact",
            Self::InitializeBinArray => return "initialize_bin_array",
            Self::AddLiquidity => return "add_liquidity",
            Self::RemoveLiquidity => return "remove_liquidity",
            Self::ClaimFee2 => return "claim_fee2",
            Self::InitializePosition => return "initialize_position",
            Self::Unknown => return "unknown",
        }
    }

    fn kind(&self) -> MeteoraDlmmInstructionKind {
        match self {
            Self::InitializeLbPair
            | Self::InitializeLbPair2
            | Self::InitializeCustomizablePermissionlessLbPair
            | Self::InitializeCustomizablePermissionlessLbPair2 => {
                return MeteoraDlmmInstructionKind::CreatePool;
            },
            Self::Swap
            | Self::Swap2
            | Self::SwapExactOut
            | Self::SwapExactOut2
            | Self::SwapWithPriceImpact => return MeteoraDlmmInstructionKind::Swap,
            Self::AddLiquidity => return MeteoraDlmmInstructionKind::LiquidityAdd,
            Self::RemoveLiquidity => return MeteoraDlmmInstructionKind::LiquidityRemove,
            Self::InitializePosition => return MeteoraDlmmInstructionKind::PositionOpen,
            Self::InitializeBinArray => return MeteoraDlmmInstructionKind::PoolLifecycle,
            Self::Unknown => return MeteoraDlmmInstructionKind::Unknown,
            Self::ClaimFee2 => {
                return MeteoraDlmmInstructionKind::Ignore;
            },
        }
    }
}

/// Meteora DLMM decoder.
#[derive(Debug, Clone, Default)]
pub struct MeteoraDlmmDecoder;

impl MeteoraDlmmDecoder {
    /// Creates a new decoder.
    pub fn new() -> Self {
        return Self;
    }

    /// Decodes one projected transaction into zero or more Meteora DLMM events.
    pub fn decode_transaction(
        &self,
        transaction: &crate::ChainTransactionDto,
        instructions: &[crate::ChainInstructionDto],
    ) -> Result<std::vec::Vec<crate::MeteoraDlmmDecodedEvent>, crate::Error> {
        let transaction_id = match transaction.id {
            Some(transaction_id) => transaction_id,
            None => {
                return Err(crate::Error::InvalidState(format!(
                    "chain transaction '{}' has no internal id",
                    transaction.signature
                )));
            },
        };
        let transaction_json_result =
            serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
        let transaction_json = match transaction_json_result {
            Ok(transaction_json) => transaction_json,
            Err(error) => {
                return Err(crate::Error::Json(format!(
                    "cannot parse transaction_json for signature '{}': {}",
                    transaction.signature, error
                )));
            },
        };
        let log_messages = extract_log_messages(&transaction_json);
        let mut decoded_events = std::vec::Vec::new();
        for instruction in instructions {
            let program_id = match instruction.program_id.as_deref() {
                Some(program_id) => program_id,
                None => continue,
            };
            if program_id != crate::METEORA_DLMM_PROGRAM_ID {
                continue;
            }
            let instruction_id = match instruction.id {
                Some(instruction_id) => instruction_id,
                None => continue,
            };
            let accounts_result = parse_accounts_json(instruction.accounts_json.as_str());
            let accounts = match accounts_result {
                Ok(accounts) => accounts,
                Err(error) => return Err(error),
            };
            let parsed_json_result = parse_optional_parsed_json(instruction.parsed_json.as_ref());
            let parsed_json = match parsed_json_result {
                Ok(parsed_json) => parsed_json,
                Err(error) => return Err(error),
            };
            let instruction_data_result =
                decode_instruction_data_json(instruction.data_json.as_ref());
            let instruction_data = match instruction_data_result {
                Ok(instruction_data) => instruction_data,
                Err(error) => return Err(error),
            };
            let instruction_name = classify_instruction_name(
                parsed_json.as_ref(),
                instruction.parsed_type.as_deref(),
                instruction_data.as_deref(),
                &log_messages,
            );
            let instruction_kind = instruction_name.kind();
            if instruction_kind == MeteoraDlmmInstructionKind::Unknown
                || instruction_kind == MeteoraDlmmInstructionKind::Ignore
            {
                continue;
            }
            let pool_account =
                resolve_dlmm_pool_account(instruction_name, parsed_json.as_ref(), &accounts);
            let token_a_mint =
                resolve_dlmm_token_x_mint(instruction_name, parsed_json.as_ref(), &accounts);
            let token_b_mint =
                resolve_dlmm_token_y_mint(instruction_name, parsed_json.as_ref(), &accounts);
            if pool_account.is_none() {
                continue;
            }
            let config_account =
                resolve_dlmm_config_account(instruction_name, parsed_json.as_ref(), &accounts);
            if instruction_kind == MeteoraDlmmInstructionKind::CreatePool {
                if token_a_mint.is_none() || token_b_mint.is_none() {
                    continue;
                }
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": "create_pool",
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": "create_pool",
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint,
                    "configAccount": config_account
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::CreatePool(
                    crate::MeteoraDlmmCreatePoolDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        config_account,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::Swap {
                if token_a_mint.is_none() || token_b_mint.is_none() {
                    continue;
                }
                let reserve_x_account = resolve_dlmm_reserve_x_account(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let reserve_y_account = resolve_dlmm_reserve_y_account(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let user_token_in_account = resolve_dlmm_user_token_in_account(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let user_token_out_account = resolve_dlmm_user_token_out_account(
                    instruction_name,
                    parsed_json.as_ref(),
                    &accounts,
                );
                let trade_side = infer_trade_side(parsed_json.as_ref());
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": "swap",
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": "swap",
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint,
                    "reserveXAccount": reserve_x_account,
                    "reserveYAccount": reserve_y_account,
                    "userTokenInAccount": user_token_in_account,
                    "userTokenOutAccount": user_token_out_account,
                    "tradeSide": format!("{:?}", trade_side)
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::Swap(
                    crate::MeteoraDlmmSwapDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        trade_side,
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        reserve_x_account,
                        reserve_y_account,
                        user_token_in_account,
                        user_token_out_account,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::LiquidityAdd
                || instruction_kind == MeteoraDlmmInstructionKind::LiquidityRemove
                || instruction_kind == MeteoraDlmmInstructionKind::PositionOpen
            {
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let actor_wallet =
                    resolve_dlmm_actor_wallet(instruction_name, parsed_json.as_ref(), &accounts);
                let base_amount_raw = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "baseAmountRaw",
                        "base_amount_raw",
                        "tokenXAmount",
                        "token_x_amount",
                        "amountX",
                        "amount_x",
                    ],
                );
                let quote_amount_raw = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "quoteAmountRaw",
                        "quote_amount_raw",
                        "tokenYAmount",
                        "token_y_amount",
                        "amountY",
                        "amount_y",
                    ],
                );
                let liquidity_amount_raw = extract_amount_string_by_candidate_keys(
                    parsed_json.as_ref(),
                    &[
                        "liquidity",
                        "liquidityAmount",
                        "liquidity_amount",
                        "binLiquidity",
                        "bin_liquidity",
                    ],
                );
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint,
                    "actorWallet": actor_wallet,
                    "baseAmountRaw": base_amount_raw,
                    "quoteAmountRaw": quote_amount_raw,
                    "liquidityAmountRaw": liquidity_amount_raw
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::Liquidity(
                    crate::MeteoraDlmmLiquidityDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        actor_wallet,
                        base_amount_raw,
                        quote_amount_raw,
                        liquidity_amount_raw,
                        payload_json,
                    },
                ));
                continue;
            }
            if instruction_kind == MeteoraDlmmInstructionKind::PoolLifecycle {
                let event_kind = format!("meteora_dlmm.{}", instruction_name.as_str());
                let payload_json = serde_json::json!({
                    "decoder": "meteora_dlmm",
                    "eventKind": instruction_name.as_str(),
                    "decodedInstructionName": instruction_name.as_str(),
                    "dataDiscriminatorHex": instruction_data
                        .as_ref()
                        .and_then(|data| return first_8_bytes_hex(data.as_slice())),
                    "classifiedInstructionKind": crate::classify_dex_event_lifecycle_kind_code(event_kind.as_str()),
                    "signature": transaction.signature,
                    "instructionId": instruction_id,
                    "parentInstructionId": instruction.parent_instruction_id,
                    "instructionIndex": instruction.instruction_index,
                    "innerInstructionIndex": instruction.inner_instruction_index,
                    "stackHeight": instruction.stack_height,
                    "accounts": accounts,
                    "parsed": parsed_json,
                    "logMessages": log_messages,
                    "poolAccount": pool_account,
                    "tokenAMint": token_a_mint,
                    "tokenBMint": token_b_mint
                });
                decoded_events.push(crate::MeteoraDlmmDecodedEvent::PoolLifecycle(
                    crate::MeteoraDlmmPoolLifecycleDecoded {
                        transaction_id,
                        instruction_id,
                        signature: transaction.signature.clone(),
                        program_id: program_id.to_string(),
                        event_kind,
                        pool_account,
                        token_a_mint,
                        token_b_mint,
                        payload_json,
                    },
                ));
            }
        }
        return Ok(decoded_events);
    }
}

fn classify_instruction_name(
    parsed_json: std::option::Option<&serde_json::Value>,
    parsed_type: std::option::Option<&str>,
    instruction_data: std::option::Option<&[u8]>,
    log_messages: &[std::string::String],
) -> MeteoraDlmmInstructionName {
    let from_data = classify_instruction_name_from_data(instruction_data);
    if from_data != MeteoraDlmmInstructionName::Unknown {
        return from_data;
    }
    if instruction_data.is_some() {
        return MeteoraDlmmInstructionName::Unknown;
    }
    if contains_create_pool_hint(parsed_type) {
        return MeteoraDlmmInstructionName::InitializeLbPair;
    }
    if contains_swap_hint(parsed_type) {
        return MeteoraDlmmInstructionName::Swap;
    }
    if contains_add_liquidity_hint(parsed_type) {
        return MeteoraDlmmInstructionName::AddLiquidity;
    }
    if contains_remove_liquidity_hint(parsed_type) {
        return MeteoraDlmmInstructionName::RemoveLiquidity;
    }
    if contains_initialize_position_hint(parsed_type) {
        return MeteoraDlmmInstructionName::InitializePosition;
    }
    if contains_initialize_bin_array_hint(parsed_type) {
        return MeteoraDlmmInstructionName::InitializeBinArray;
    }
    if parsed_type.is_some() {
        return MeteoraDlmmInstructionName::Unknown;
    }
    if let Some(parsed_json) = parsed_json {
        if contains_create_pool_hint_in_value(parsed_json) {
            return MeteoraDlmmInstructionName::InitializeLbPair;
        }
        if contains_swap_hint_in_value(parsed_json) {
            return MeteoraDlmmInstructionName::Swap;
        }
        if contains_add_liquidity_hint_in_value(parsed_json) {
            return MeteoraDlmmInstructionName::AddLiquidity;
        }
        if contains_remove_liquidity_hint_in_value(parsed_json) {
            return MeteoraDlmmInstructionName::RemoveLiquidity;
        }
        if contains_initialize_position_hint_in_value(parsed_json) {
            return MeteoraDlmmInstructionName::InitializePosition;
        }
        if contains_initialize_bin_array_hint_in_value(parsed_json) {
            return MeteoraDlmmInstructionName::InitializeBinArray;
        }
        return MeteoraDlmmInstructionName::Unknown;
    }
    for log_message in log_messages {
        if contains_create_pool_hint(Some(log_message.as_str())) {
            return MeteoraDlmmInstructionName::InitializeLbPair;
        }
        if contains_swap_hint(Some(log_message.as_str())) {
            return MeteoraDlmmInstructionName::Swap;
        }
        if contains_add_liquidity_hint(Some(log_message.as_str())) {
            return MeteoraDlmmInstructionName::AddLiquidity;
        }
        if contains_remove_liquidity_hint(Some(log_message.as_str())) {
            return MeteoraDlmmInstructionName::RemoveLiquidity;
        }
        if contains_initialize_position_hint(Some(log_message.as_str())) {
            return MeteoraDlmmInstructionName::InitializePosition;
        }
        if contains_initialize_bin_array_hint(Some(log_message.as_str())) {
            return MeteoraDlmmInstructionName::InitializeBinArray;
        }
    }
    return MeteoraDlmmInstructionName::Unknown;
}

fn classify_instruction_name_from_data(
    instruction_data: std::option::Option<&[u8]>,
) -> MeteoraDlmmInstructionName {
    let instruction_data = match instruction_data {
        Some(instruction_data) => instruction_data,
        None => return MeteoraDlmmInstructionName::Unknown,
    };
    if instruction_data.len() < 8 {
        return MeteoraDlmmInstructionName::Unknown;
    }
    let discriminator = [
        instruction_data[0],
        instruction_data[1],
        instruction_data[2],
        instruction_data[3],
        instruction_data[4],
        instruction_data[5],
        instruction_data[6],
        instruction_data[7],
    ];
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_LB_PAIR {
        return MeteoraDlmmInstructionName::InitializeLbPair;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_LB_PAIR2 {
        return MeteoraDlmmInstructionName::InitializeLbPair2;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_LB_PAIR {
        return MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_CUSTOMIZABLE_PERMISSIONLESS_LB_PAIR2 {
        return MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP {
        return MeteoraDlmmInstructionName::Swap;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP2 {
        return MeteoraDlmmInstructionName::Swap2;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP_EXACT_OUT {
        return MeteoraDlmmInstructionName::SwapExactOut;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP_EXACT_OUT2 {
        return MeteoraDlmmInstructionName::SwapExactOut2;
    }
    if discriminator == DLMM_DISCRIMINATOR_SWAP_WITH_PRICE_IMPACT {
        return MeteoraDlmmInstructionName::SwapWithPriceImpact;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_BIN_ARRAY {
        return MeteoraDlmmInstructionName::InitializeBinArray;
    }
    if discriminator == DLMM_DISCRIMINATOR_ADD_LIQUIDITY {
        return MeteoraDlmmInstructionName::AddLiquidity;
    }
    if discriminator == DLMM_DISCRIMINATOR_REMOVE_LIQUIDITY {
        return MeteoraDlmmInstructionName::RemoveLiquidity;
    }
    if discriminator == DLMM_DISCRIMINATOR_CLAIM_FEE2 {
        return MeteoraDlmmInstructionName::ClaimFee2;
    }
    if discriminator == DLMM_DISCRIMINATOR_INITIALIZE_POSITION {
        return MeteoraDlmmInstructionName::InitializePosition;
    }
    return MeteoraDlmmInstructionName::Unknown;
}

fn resolve_dlmm_pool_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "lbPair",
            "lb_pair",
            "lbPairAccount",
            "pair",
            "pairAccount",
            "pool",
            "poolAccount",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializeLbPair
        | MeteoraDlmmInstructionName::InitializeLbPair2
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2
        | MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::InitializeBinArray => {
            return extract_account(accounts, 0);
        },
        MeteoraDlmmInstructionName::AddLiquidity | MeteoraDlmmInstructionName::RemoveLiquidity => {
            return extract_account(accounts, 1);
        },
        MeteoraDlmmInstructionName::InitializePosition => {
            return extract_account(accounts, 2);
        },
        MeteoraDlmmInstructionName::ClaimFee2 | MeteoraDlmmInstructionName::Unknown => {
            return None;
        },
    }
}

fn resolve_dlmm_token_x_mint(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "tokenXMint",
            "token_x_mint",
            "tokenMintX",
            "token_mint_x",
            "mintX",
            "mint_x",
            "tokenAMint",
            "token_a_mint",
            "baseMint",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializeLbPair
        | MeteoraDlmmInstructionName::InitializeLbPair2
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2 => {
            return extract_account(accounts, 2);
        },
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact => {
            return extract_account(accounts, 6);
        },
        MeteoraDlmmInstructionName::AddLiquidity | MeteoraDlmmInstructionName::RemoveLiquidity => {
            return extract_account(accounts, 7);
        },
        MeteoraDlmmInstructionName::ClaimFee2
        | MeteoraDlmmInstructionName::InitializeBinArray
        | MeteoraDlmmInstructionName::InitializePosition
        | MeteoraDlmmInstructionName::Unknown => return None,
    }
}

fn resolve_dlmm_token_y_mint(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "tokenYMint",
            "token_y_mint",
            "tokenMintY",
            "token_mint_y",
            "mintY",
            "mint_y",
            "tokenBMint",
            "token_b_mint",
            "quoteMint",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializeLbPair
        | MeteoraDlmmInstructionName::InitializeLbPair2
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2 => {
            return extract_account(accounts, 3);
        },
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact => {
            return extract_account(accounts, 7);
        },
        MeteoraDlmmInstructionName::AddLiquidity | MeteoraDlmmInstructionName::RemoveLiquidity => {
            return extract_account(accounts, 8);
        },
        MeteoraDlmmInstructionName::ClaimFee2
        | MeteoraDlmmInstructionName::InitializeBinArray
        | MeteoraDlmmInstructionName::InitializePosition
        | MeteoraDlmmInstructionName::Unknown => return None,
    }
}

fn resolve_dlmm_reserve_x_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &["reserveX", "reserve_x", "reserveXAccount", "reserve_x_account"],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact => {
            return extract_account(accounts, 2);
        },
        _ => return None,
    }
}

fn resolve_dlmm_reserve_y_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &["reserveY", "reserve_y", "reserveYAccount", "reserve_y_account"],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact => {
            return extract_account(accounts, 3);
        },
        _ => return None,
    }
}

fn resolve_dlmm_user_token_in_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &["userTokenIn", "user_token_in", "userTokenInAccount", "user_token_in_account"],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapExactOut2
        | MeteoraDlmmInstructionName::SwapWithPriceImpact => {
            return extract_account(accounts, 4);
        },
        _ => return None,
    }
}

fn resolve_dlmm_user_token_out_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "userTokenOut",
            "user_token_out",
            "userTokenOutAccount",
            "user_token_out_account",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::Swap
        | MeteoraDlmmInstructionName::Swap2
        | MeteoraDlmmInstructionName::SwapExactOut
        | MeteoraDlmmInstructionName::SwapWithPriceImpact
        | MeteoraDlmmInstructionName::SwapExactOut2 => {
            return extract_account(accounts, 5);
        },
        _ => return None,
    }
}

fn resolve_dlmm_config_account(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &[
            "presetParameter",
            "preset_parameter",
            "presetParameter2",
            "config",
            "poolConfig",
        ],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::InitializeLbPair
        | MeteoraDlmmInstructionName::InitializeLbPair2
        | MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair => {
            return extract_account(accounts, 7);
        },
        MeteoraDlmmInstructionName::InitializeCustomizablePermissionlessLbPair2 => {
            return None;
        },
        _ => return None,
    }
}

fn resolve_dlmm_actor_wallet(
    instruction_name: MeteoraDlmmInstructionName,
    parsed_json: std::option::Option<&serde_json::Value>,
    accounts: &[std::string::String],
) -> std::option::Option<std::string::String> {
    let parsed_value = extract_string_by_candidate_keys(
        parsed_json,
        &["owner", "payer", "sender", "user", "authority", "liquidityProvider"],
    );
    if parsed_value.is_some() {
        return parsed_value;
    }
    match instruction_name {
        MeteoraDlmmInstructionName::AddLiquidity | MeteoraDlmmInstructionName::RemoveLiquidity => {
            return extract_account(accounts, 9);
        },
        MeteoraDlmmInstructionName::InitializePosition => {
            return extract_account(accounts, 3);
        },
        _ => return None,
    }
}

fn extract_amount_string_by_candidate_keys(
    value: std::option::Option<&serde_json::Value>,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    let value = match value {
        Some(value) => value,
        None => return None,
    };
    if let Some(text) = extract_string_by_candidate_keys(Some(value), candidate_keys) {
        return Some(text);
    }
    return extract_number_by_candidate_keys(Some(value), candidate_keys);
}

fn extract_number_by_candidate_keys(
    value: std::option::Option<&serde_json::Value>,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    let value = match value {
        Some(value) => value,
        None => return None,
    };
    match value {
        serde_json::Value::Object(object) => {
            for candidate_key in candidate_keys {
                if let Some(candidate) = object.get(*candidate_key) {
                    if let Some(number) = candidate.as_i64() {
                        return Some(number.to_string());
                    }
                    if let Some(number) = candidate.as_u64() {
                        return Some(number.to_string());
                    }
                    if let Some(number) = candidate.as_f64() {
                        return Some(number.to_string());
                    }
                }
            }
            for nested in object.values() {
                let result = extract_number_by_candidate_keys(Some(nested), candidate_keys);
                if result.is_some() {
                    return result;
                }
            }
        },
        serde_json::Value::Array(values) => {
            for nested in values {
                let result = extract_number_by_candidate_keys(Some(nested), candidate_keys);
                if result.is_some() {
                    return result;
                }
            }
        },
        _ => {},
    }
    return None;
}

fn contains_create_pool_hint(value: std::option::Option<&str>) -> bool {
    let value = match value {
        Some(value) => value.to_ascii_lowercase(),
        None => return false,
    };
    if value.contains("initializelbpair") {
        return true;
    }
    if value.contains("initialize_lb_pair") {
        return true;
    }
    if value.contains("initializecustomizablepermissionlesslbpair") {
        return true;
    }
    if value.contains("initialize_customizable_permissionless_lb_pair") {
        return true;
    }
    if value.contains("initializepermissionlbpair") {
        return true;
    }
    if value.contains("initialize_permission_lb_pair") {
        return true;
    }
    if value.contains("create_lb_pair") {
        return true;
    }
    return false;
}

fn contains_swap_hint(value: std::option::Option<&str>) -> bool {
    let value = match value {
        Some(value) => value.to_ascii_lowercase(),
        None => return false,
    };
    if value.contains("swap") {
        return true;
    }
    return false;
}

fn contains_add_liquidity_hint(value: std::option::Option<&str>) -> bool {
    let value = match value {
        Some(value) => value.to_ascii_lowercase(),
        None => return false,
    };
    if value.contains("addliquidity") {
        return true;
    }
    if value.contains("add_liquidity") {
        return true;
    }
    return false;
}

fn contains_remove_liquidity_hint(value: std::option::Option<&str>) -> bool {
    let value = match value {
        Some(value) => value.to_ascii_lowercase(),
        None => return false,
    };
    if value.contains("removeliquidity") {
        return true;
    }
    if value.contains("remove_liquidity") {
        return true;
    }
    return false;
}

fn contains_initialize_position_hint(value: std::option::Option<&str>) -> bool {
    let value = match value {
        Some(value) => value.to_ascii_lowercase(),
        None => return false,
    };
    if value.contains("initializeposition") {
        return true;
    }
    if value.contains("initialize_position") {
        return true;
    }
    return false;
}

fn contains_initialize_bin_array_hint(value: std::option::Option<&str>) -> bool {
    let value = match value {
        Some(value) => value.to_ascii_lowercase(),
        None => return false,
    };
    if value.contains("initializebinarray") {
        return true;
    }
    if value.contains("initialize_bin_array") {
        return true;
    }
    return false;
}

fn contains_create_pool_hint_in_value(value: &serde_json::Value) -> bool {
    return contains_string_hint_in_value(value, contains_create_pool_hint);
}

fn contains_swap_hint_in_value(value: &serde_json::Value) -> bool {
    return contains_string_hint_in_value(value, contains_swap_hint);
}

fn contains_add_liquidity_hint_in_value(value: &serde_json::Value) -> bool {
    return contains_string_hint_in_value(value, contains_add_liquidity_hint);
}

fn contains_remove_liquidity_hint_in_value(value: &serde_json::Value) -> bool {
    return contains_string_hint_in_value(value, contains_remove_liquidity_hint);
}

fn contains_initialize_position_hint_in_value(value: &serde_json::Value) -> bool {
    return contains_string_hint_in_value(value, contains_initialize_position_hint);
}

fn contains_initialize_bin_array_hint_in_value(value: &serde_json::Value) -> bool {
    return contains_string_hint_in_value(value, contains_initialize_bin_array_hint);
}

fn contains_string_hint_in_value(
    value: &serde_json::Value,
    predicate: fn(std::option::Option<&str>) -> bool,
) -> bool {
    match value {
        serde_json::Value::String(text) => return predicate(Some(text.as_str())),
        serde_json::Value::Array(values) => {
            for nested in values {
                if contains_string_hint_in_value(nested, predicate) {
                    return true;
                }
            }
        },
        serde_json::Value::Object(object) => {
            for nested in object.values() {
                if contains_string_hint_in_value(nested, predicate) {
                    return true;
                }
            }
        },
        _ => {},
    }
    return false;
}

fn infer_trade_side(parsed_json: std::option::Option<&serde_json::Value>) -> crate::SwapTradeSide {
    let side = extract_string_by_candidate_keys(
        parsed_json,
        &["tradeSide", "trade_side", "side", "swapSide", "swap_side"],
    );
    match side.as_deref() {
        Some("BuyBase") => return crate::SwapTradeSide::BuyBase,
        Some("buy") => return crate::SwapTradeSide::BuyBase,
        Some("BUY") => return crate::SwapTradeSide::BuyBase,
        Some("SellBase") => return crate::SwapTradeSide::SellBase,
        Some("sell") => return crate::SwapTradeSide::SellBase,
        Some("SELL") => return crate::SwapTradeSide::SellBase,
        _ => return crate::SwapTradeSide::Unknown,
    }
}

fn extract_log_messages(
    transaction_json: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
    let mut messages = std::vec::Vec::new();
    let meta = match transaction_json.get("meta") {
        Some(meta) => meta,
        None => return messages,
    };
    let logs = match meta.get("logMessages").and_then(|value| return value.as_array()) {
        Some(logs) => logs,
        None => return messages,
    };
    for log in logs {
        if let Some(text) = log.as_str() {
            messages.push(text.to_string());
        }
    }
    return messages;
}

fn parse_accounts_json(
    accounts_json: &str,
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
    let parsed_result = serde_json::from_str::<serde_json::Value>(accounts_json);
    let parsed = match parsed_result {
        Ok(parsed) => parsed,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse Meteora DLMM accounts_json: {}",
                error
            )));
        },
    };
    let array = match parsed.as_array() {
        Some(array) => array,
        None => return Ok(std::vec::Vec::new()),
    };
    let mut accounts = std::vec::Vec::new();
    for item in array {
        if let Some(text) = item.as_str() {
            accounts.push(text.to_string());
            continue;
        }
        if let Some(pubkey) = item.get("pubkey").and_then(|value| return value.as_str()) {
            accounts.push(pubkey.to_string());
        }
    }
    return Ok(accounts);
}

fn parse_optional_parsed_json(
    parsed_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, crate::Error> {
    let parsed_json = match parsed_json {
        Some(parsed_json) => parsed_json,
        None => return Ok(None),
    };
    let parsed_result = serde_json::from_str::<serde_json::Value>(parsed_json);
    match parsed_result {
        Ok(parsed) => return Ok(Some(parsed)),
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse Meteora DLMM parsed_json: {}",
                error
            )));
        },
    }
}

fn extract_account(
    accounts: &[std::string::String],
    index: usize,
) -> std::option::Option<std::string::String> {
    match accounts.get(index) {
        Some(account) => return Some(account.clone()),
        None => return None,
    }
}

fn extract_string_by_candidate_keys(
    value: std::option::Option<&serde_json::Value>,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    let value = match value {
        Some(value) => value,
        None => return None,
    };
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            if let Some(found) = object.get(*candidate_key) {
                if let Some(text) = found.as_str() {
                    return Some(text.to_string());
                }
                if let Some(number) = found.as_u64() {
                    return Some(number.to_string());
                }
                if let Some(number) = found.as_i64() {
                    return Some(number.to_string());
                }
            }
        }
        for nested in object.values() {
            let nested_result = extract_string_by_candidate_keys(Some(nested), candidate_keys);
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    if let Some(array) = value.as_array() {
        for nested in array {
            let nested_result = extract_string_by_candidate_keys(Some(nested), candidate_keys);
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    return None;
}

fn decode_instruction_data_json(
    data_json: std::option::Option<&std::string::String>,
) -> Result<std::option::Option<std::vec::Vec<u8>>, crate::Error> {
    let data_json = match data_json {
        Some(data_json) => data_json,
        None => return Ok(None),
    };
    let parsed_result = serde_json::from_str::<serde_json::Value>(data_json.as_str());
    let parsed = match parsed_result {
        Ok(parsed) => parsed,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse Meteora DLMM data_json: {}",
                error
            )));
        },
    };
    if let serde_json::Value::String(base58_text) = parsed {
        let decoded_result = decode_base58(base58_text.as_str());
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => return Err(error),
        };
        return Ok(Some(decoded));
    }
    return Ok(None);
}

fn decode_base58(input: &str) -> Result<std::vec::Vec<u8>, crate::Error> {
    let decoded_result = bs58::decode(input).into_vec();
    match decoded_result {
        Ok(decoded) => return Ok(decoded),
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot decode Meteora DLMM instruction data from base58: {}",
                error
            )));
        },
    }
}

fn first_8_bytes_hex(bytes: &[u8]) -> std::option::Option<std::string::String> {
    if bytes.len() < 8 {
        return None;
    }
    return Some(format!(
        "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
    ));
}

#[cfg(test)]
mod tests {
    fn make_create_transaction() -> crate::ChainTransactionDto {
        let mut dto = crate::ChainTransactionDto::new(
            "sig-meteora-dlmm-create-1".to_string(),
            Some(888101),
            Some(1779400001),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 888101,
                "meta": {
                    "logMessages": [
                        "Program log: Instruction: InitializeLbPair"
                    ]
                },
                "transaction": {
                    "message": {
                        "instructions": []
                    }
                }
            })
            .to_string(),
        );
        dto.id = Some(401);
        return dto;
    }

    fn make_create_instruction() -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            401,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora-dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "DlmmPair111",
                "DlmmTokenX111",
                crate::WSOL_MINT_ID,
                "DlmmPreset111"
            ])
            .to_string(),
            None,
            Some("initialize_lb_pair".to_string()),
            Some(
                serde_json::json!({
                    "info": {
                        "lbPair": "DlmmPair111",
                        "tokenXMint": "DlmmTokenX111",
                        "tokenYMint": crate::WSOL_MINT_ID,
                        "presetParameter": "DlmmPreset111"
                    }
                })
                .to_string(),
            ),
        );
        dto.id = Some(402);
        return dto;
    }

    fn make_swap_transaction() -> crate::ChainTransactionDto {
        let mut dto = crate::ChainTransactionDto::new(
            "sig-meteora-dlmm-swap-1".to_string(),
            Some(888102),
            Some(1779400002),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 888102,
                "meta": {
                    "logMessages": [
                        "Program log: Instruction: Swap"
                    ]
                },
                "transaction": {
                    "message": {
                        "instructions": []
                    }
                }
            })
            .to_string(),
        );
        dto.id = Some(403);
        return dto;
    }

    fn make_swap_instruction() -> crate::ChainInstructionDto {
        let mut dto = crate::ChainInstructionDto::new(
            403,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora-dlmm".to_string()),
            Some(1),
            serde_json::json!(["DlmmPairSwap111", "DlmmSwapTokenX111", crate::WSOL_MINT_ID])
                .to_string(),
            None,
            Some("swap".to_string()),
            Some(
                serde_json::json!({
                    "info": {
                        "lbPair": "DlmmPairSwap111",
                        "tokenXMint": "DlmmSwapTokenX111",
                        "tokenYMint": crate::WSOL_MINT_ID,
                        "tradeSide": "buy"
                    }
                })
                .to_string(),
            ),
        );
        dto.id = Some(404);
        return dto;
    }

    #[test]
    fn meteora_dlmm_create_pool_is_detected() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = make_create_transaction();
        let instructions = vec![make_create_instruction()];
        let decoded_result = decoder.decode_transaction(&transaction, &instructions);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDlmmDecodedEvent::CreatePool(event) => {
                assert_eq!(event.transaction_id, 401);
                assert_eq!(event.instruction_id, 402);
                assert_eq!(event.pool_account, Some("DlmmPair111".to_string()));
                assert_eq!(event.token_a_mint, Some("DlmmTokenX111".to_string()));
                assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
                assert_eq!(event.config_account, Some("DlmmPreset111".to_string()));
            },
            crate::MeteoraDlmmDecodedEvent::Swap(_) => {
                panic!("unexpected swap event");
            },
            crate::MeteoraDlmmDecodedEvent::Liquidity(_)
            | crate::MeteoraDlmmDecodedEvent::PoolLifecycle(_) => {
                panic!("unexpected non-trade event");
            },
        }
    }

    #[test]
    fn meteora_dlmm_swap_is_detected() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = make_swap_transaction();
        let instructions = vec![make_swap_instruction()];
        let decoded_result = decoder.decode_transaction(&transaction, &instructions);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDlmmDecodedEvent::Swap(event) => {
                assert_eq!(event.transaction_id, 403);
                assert_eq!(event.instruction_id, 404);
                assert_eq!(event.pool_account, Some("DlmmPairSwap111".to_string()));
                assert_eq!(event.token_a_mint, Some("DlmmSwapTokenX111".to_string()));
                assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
                assert_eq!(event.trade_side, crate::SwapTradeSide::BuyBase);
            },
            crate::MeteoraDlmmDecodedEvent::CreatePool(_) => {
                panic!("unexpected create event");
            },
            crate::MeteoraDlmmDecodedEvent::Liquidity(_)
            | crate::MeteoraDlmmDecodedEvent::PoolLifecycle(_) => {
                panic!("unexpected non-trade event");
            },
        }
    }

    #[test]
    fn meteora_dlmm_initialize_bin_array_hint_is_decoded_as_pool_lifecycle() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = make_swap_transaction();
        let mut instruction = make_swap_instruction();
        instruction.parsed_type = Some("initialize_bin_array".to_string());
        instruction.parsed_json = None;
        let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDlmmDecodedEvent::PoolLifecycle(event) => {
                assert_eq!(event.event_kind, "meteora_dlmm.initialize_bin_array");
                assert_eq!(event.pool_account, Some("DlmmPairSwap111".to_string()));
            },
            _ => panic!("expected pool lifecycle event"),
        }
    }

    #[test]
    fn meteora_dlmm_swap2_discriminator_is_detected() {
        let instruction_data = [0x41, 0x4b, 0x3f, 0x4c, 0xeb, 0x5b, 0x5b, 0x88, 0x01, 0x02, 0x03];
        let name = super::classify_instruction_name_from_data(Some(&instruction_data));
        assert_eq!(name, super::MeteoraDlmmInstructionName::Swap2);
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::Swap);
    }

    #[test]
    fn meteora_dlmm_swap_accounts_are_mapped_from_carbon_layout() {
        let accounts = vec![
            "LbPair111".to_string(),
            "Bitmap111".to_string(),
            "ReserveX111".to_string(),
            "ReserveY111".to_string(),
            "UserTokenIn111".to_string(),
            "UserTokenOut111".to_string(),
            "TokenXMint111".to_string(),
            "TokenYMint111".to_string(),
        ];
        let pool = super::resolve_dlmm_pool_account(
            super::MeteoraDlmmInstructionName::Swap2,
            None,
            &accounts,
        );
        let token_x = super::resolve_dlmm_token_x_mint(
            super::MeteoraDlmmInstructionName::Swap2,
            None,
            &accounts,
        );
        let token_y = super::resolve_dlmm_token_y_mint(
            super::MeteoraDlmmInstructionName::Swap2,
            None,
            &accounts,
        );
        assert_eq!(pool, Some("LbPair111".to_string()));
        assert_eq!(token_x, Some("TokenXMint111".to_string()));
        assert_eq!(token_y, Some("TokenYMint111".to_string()));
    }

    #[test]
    fn meteora_dlmm_real_swap2_base58_discriminator_is_decoded() {
        let data_json = "\"fx9RHbGFfZ8bH9v4Jv5SQRfuGUq9kGrWfmiZ99\"".to_string();
        let decoded_result = super::decode_instruction_data_json(Some(&data_json));
        let decoded = match decoded_result {
            Ok(Some(decoded)) => decoded,
            Ok(None) => panic!("expected decoded data"),
            Err(error) => panic!("decode must succeed: {}", error),
        };
        let name = super::classify_instruction_name_from_data(Some(decoded.as_slice()));
        assert_eq!(name, super::MeteoraDlmmInstructionName::Swap2);
    }

    #[test]
    fn meteora_dlmm_inner_swap2_instruction_is_not_skipped() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let mut transaction = crate::ChainTransactionDto::new(
            "sig-meteora-dlmm-inner-swap2".to_string(),
            Some(888103),
            Some(1779400003),
            Some("helius_primary_http".to_string()),
            Some("0".to_string()),
            None,
            None,
            serde_json::json!({
                "slot": 888103,
                "meta": {
                    "logMessages": []
                },
                "transaction": {
                    "message": {
                        "instructions": []
                    }
                }
            })
            .to_string(),
        );
        transaction.id = Some(405);
        let mut instruction = crate::ChainInstructionDto::new(
            405,
            Some(404),
            3,
            Some(14),
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora-dlmm".to_string()),
            Some(2),
            serde_json::json!([
                "LbPair111",
                "Bitmap111",
                "ReserveX111",
                "ReserveY111",
                "UserTokenIn111",
                "UserTokenOut111",
                "TokenXMint111",
                "TokenYMint111",
                "Oracle111",
                "HostFee111",
                "User111",
                crate::SPL_TOKEN_PROGRAM_ID,
                crate::SPL_TOKEN_PROGRAM_ID,
                "EventAuthority111".to_string(),
                crate::METEORA_DLMM_PROGRAM_ID,
                "BinArray111",
                "BinArray222"
            ])
            .to_string(),
            Some("\"fx9RHbGFfZ8bH9v4Jv5SQRfuGUq9kGrWfmiZ99\"".to_string()),
            None,
            None,
        );
        instruction.id = Some(406);
        let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDlmmDecodedEvent::Swap(event) => {
                assert_eq!(event.transaction_id, 405);
                assert_eq!(event.instruction_id, 406);
                assert_eq!(event.pool_account, Some("LbPair111".to_string()));
                assert_eq!(event.token_a_mint, Some("TokenXMint111".to_string()));
                assert_eq!(event.token_b_mint, Some("TokenYMint111".to_string()));
                assert_eq!(event.reserve_x_account, Some("ReserveX111".to_string()));
                assert_eq!(event.reserve_y_account, Some("ReserveY111".to_string()));
                assert_eq!(event.user_token_in_account, Some("UserTokenIn111".to_string()));
                assert_eq!(event.user_token_out_account, Some("UserTokenOut111".to_string()));
            },
            crate::MeteoraDlmmDecodedEvent::CreatePool(_) => {
                panic!("unexpected create event");
            },
            crate::MeteoraDlmmDecodedEvent::Liquidity(_)
            | crate::MeteoraDlmmDecodedEvent::PoolLifecycle(_) => {
                panic!("unexpected non-trade event");
            },
        }
    }

    #[test]
    fn meteora_dlmm_initialize_position_discriminator_is_non_trade_position_open() {
        let instruction_data = [0xdb, 0xc0, 0xea, 0x47, 0xbe, 0xbf, 0x66, 0x50, 0x01, 0x02, 0x03];
        let log_messages = vec!["Program log: Instruction: Swap".to_string()];
        let name =
            super::classify_instruction_name(None, None, Some(&instruction_data), &log_messages);
        assert_eq!(name, super::MeteoraDlmmInstructionName::InitializePosition);
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::PositionOpen);
    }

    #[test]
    fn meteora_dlmm_add_remove_liquidity_discriminators_are_non_trade_liquidity() {
        let add_data = [0xb5, 0x9d, 0x59, 0x43, 0x8f, 0xb6, 0x34, 0x48, 0x01];
        let remove_data = [0x50, 0x55, 0xd1, 0x48, 0x18, 0xce, 0xb1, 0x6c, 0x01];
        let add_name = super::classify_instruction_name_from_data(Some(&add_data));
        let remove_name = super::classify_instruction_name_from_data(Some(&remove_data));
        assert_eq!(add_name, super::MeteoraDlmmInstructionName::AddLiquidity);
        assert_eq!(add_name.kind(), super::MeteoraDlmmInstructionKind::LiquidityAdd);
        assert_eq!(remove_name, super::MeteoraDlmmInstructionName::RemoveLiquidity);
        assert_eq!(remove_name.kind(), super::MeteoraDlmmInstructionKind::LiquidityRemove);
    }

    #[test]
    fn meteora_dlmm_initialize_bin_array_is_pool_lifecycle() {
        let data = [0x23, 0x56, 0x13, 0xb9, 0x4e, 0xd4, 0x4b, 0xd3, 0x01];
        let name = super::classify_instruction_name_from_data(Some(&data));
        assert_eq!(name, super::MeteoraDlmmInstructionName::InitializeBinArray);
        assert_eq!(name.kind(), super::MeteoraDlmmInstructionKind::PoolLifecycle);
    }

    #[test]
    fn meteora_dlmm_add_liquidity_is_decoded_as_non_trade_event() {
        let decoder = crate::MeteoraDlmmDecoder::new();
        let transaction = make_swap_transaction();
        let mut instruction = crate::ChainInstructionDto::new(
            403,
            None,
            0,
            None,
            Some(crate::METEORA_DLMM_PROGRAM_ID.to_string()),
            Some("meteora-dlmm".to_string()),
            Some(1),
            serde_json::json!([
                "Position111",
                "DlmmPairSwap111",
                "Bitmap111",
                "UserTokenX111",
                "UserTokenY111",
                "ReserveX111",
                "ReserveY111",
                "DlmmSwapTokenX111",
                crate::WSOL_MINT_ID,
                "Owner111"
            ])
            .to_string(),
            Some("\"3K5citUwVB6uv\"".to_string()),
            None,
            None,
        );
        instruction.id = Some(405);
        let decoded_result = decoder.decode_transaction(&transaction, &[instruction]);
        let decoded = match decoded_result {
            Ok(decoded) => decoded,
            Err(error) => panic!("decode must succeed: {}", error),
        };
        assert_eq!(decoded.len(), 1);
        match &decoded[0] {
            crate::MeteoraDlmmDecodedEvent::Liquidity(event) => {
                assert_eq!(event.event_kind, "meteora_dlmm.add_liquidity");
                assert_eq!(event.pool_account, Some("DlmmPairSwap111".to_string()));
                assert_eq!(event.token_a_mint, Some("DlmmSwapTokenX111".to_string()));
                assert_eq!(event.token_b_mint, Some(crate::WSOL_MINT_ID.to_string()));
                assert_eq!(event.actor_wallet, Some("Owner111".to_string()));
            },
            _ => panic!("expected liquidity event"),
        }
    }

    #[test]
    fn meteora_dlmm_unknown_data_discriminator_does_not_fallback_to_global_swap_logs() {
        let instruction_data = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x01, 0x02, 0x03];
        let log_messages = vec!["Program log: Instruction: Swap".to_string()];
        let name =
            super::classify_instruction_name(None, None, Some(&instruction_data), &log_messages);
        assert_eq!(name, super::MeteoraDlmmInstructionName::Unknown);
    }
}
