// file: kb_lib/src/meteora_swap_amount_inference.rs

//! Meteora swap amount inference from flattened CPI token transfers.
//!
//! This resolver is intentionally conservative. It only returns amounts when the
//! flattened inner-instruction window of one decoded Meteora swap contains two
//! non-zero token transfers involving token accounts owned by the decoded pool.

/// Resolved Meteora swap amounts and canonical token order.
#[derive(Debug, Clone)]
pub(crate) struct MeteoraSwapAmountInference {
    /// Canonical base token mint.
    pub(crate) base_token_mint: std::string::String,
    /// Canonical quote token mint.
    pub(crate) quote_token_mint: std::string::String,
    /// Optional base-side vault account.
    pub(crate) base_vault_address: std::option::Option<std::string::String>,
    /// Optional quote-side vault account.
    pub(crate) quote_vault_address: std::option::Option<std::string::String>,
    /// Base amount in raw token units.
    pub(crate) base_amount_raw: std::string::String,
    /// Quote amount in raw token units.
    pub(crate) quote_amount_raw: std::string::String,
    /// Trade side relative to canonical base token.
    pub(crate) trade_side: crate::SwapTradeSide,
}

#[derive(Debug, Clone)]
struct TokenAccountInfo {
    account_address: std::string::String,
    mint: std::string::String,
    owner: std::option::Option<std::string::String>,
}

#[derive(Debug, Clone)]
struct TokenTransferInfo {
    mint: std::string::String,
    amount_raw: std::string::String,
    source: std::string::String,
    destination: std::string::String,
    source_owner: std::option::Option<std::string::String>,
    destination_owner: std::option::Option<std::string::String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PoolTransferDirection {
    IntoPool,
    OutOfPool,
}

#[derive(Debug, Clone)]
struct PoolTouchedTransfer {
    mint: std::string::String,
    amount_raw: std::string::String,
    vault_address: std::option::Option<std::string::String>,
    direction: crate::meteora_swap_amount_inference::PoolTransferDirection,
}

/// Infers DAMM v1 swap amounts from the DAMM v1 swap event log payload.
///
/// DAMM v1 routes often expose the two useful amounts through the event log
/// emitted by the `Eo7...` program, while the flattened transfer window may not
/// show token accounts owned directly by the pool. The first two little-endian
/// u64 values after discriminator `516ce3becdd00ac4` are treated as the input
/// and output raw amounts for the matched DAMM v1 swap event.
pub(crate) fn infer_meteora_damm_v1_swap_amounts_from_event_log(
    transaction: &crate::ChainTransactionDto,
    accounts: &[std::string::String],
    log_messages: &[std::string::String],
    pool_account: std::option::Option<&str>,
) -> Result<
    std::option::Option<crate::meteora_swap_amount_inference::MeteoraSwapAmountInference>,
    crate::Error,
> {
    let pool_account = match pool_account {
        Some(pool_account) => pool_account.trim(),
        None => return Ok(None),
    };
    if pool_account.is_empty() {
        return Ok(None);
    }
    let input_token_account = match crate::meteora_swap_amount_inference::account_at(accounts, 1) {
        Some(input_token_account) => input_token_account,
        None => return Ok(None),
    };
    let output_token_account = match crate::meteora_swap_amount_inference::account_at(accounts, 2) {
        Some(output_token_account) => output_token_account,
        None => return Ok(None),
    };
    let base_vault = crate::meteora_swap_amount_inference::account_at(accounts, 5);
    let quote_vault = crate::meteora_swap_amount_inference::account_at(accounts, 6);
    let transaction_value_result =
        serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
    let transaction_value = match transaction_value_result {
        Ok(transaction_value) => transaction_value,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse transaction_json for Meteora DAMM v1 event-log amount inference: {}",
                error
            )));
        },
    };
    let meta_value = match transaction.meta_json.as_deref() {
        Some(meta_json) => {
            let meta_value_result = serde_json::from_str::<serde_json::Value>(meta_json);
            match meta_value_result {
                Ok(meta_value) => meta_value,
                Err(error) => {
                    return Err(crate::Error::Json(format!(
                        "cannot parse meta_json for Meteora DAMM v1 event-log amount inference: {}",
                        error
                    )));
                },
            }
        },
        None => match transaction_value.get("meta") {
            Some(meta) => meta.clone(),
            None => return Ok(None),
        },
    };
    if crate::meteora_swap_amount_inference::transaction_failed(&meta_value) {
        return Ok(None);
    }
    let account_keys = crate::meteora_swap_amount_inference::collect_transaction_account_keys(
        &transaction_value,
        &meta_value,
    );
    let token_accounts = crate::meteora_swap_amount_inference::collect_token_account_infos(
        &meta_value,
        account_keys.as_slice(),
    );
    let input_mint = match crate::meteora_swap_amount_inference::mint_for_token_account(
        token_accounts.as_slice(),
        input_token_account.as_str(),
    ) {
        Some(input_mint) => input_mint,
        None => return Ok(None),
    };
    let output_mint = match crate::meteora_swap_amount_inference::mint_for_token_account(
        token_accounts.as_slice(),
        output_token_account.as_str(),
    ) {
        Some(output_mint) => output_mint,
        None => return Ok(None),
    };
    if input_mint == output_mint {
        return Ok(None);
    }
    let event_amounts =
        crate::meteora_swap_amount_inference::extract_damm_v1_event_amounts_for_pool(
            log_messages,
            pool_account,
        );
    let event_amounts = match event_amounts {
        Some(event_amounts) => event_amounts,
        None => return Ok(None),
    };
    if event_amounts.0 == 0 || event_amounts.1 == 0 {
        return Ok(None);
    }
    return Ok(Some(crate::meteora_swap_amount_inference::MeteoraSwapAmountInference {
        base_token_mint: input_mint,
        quote_token_mint: output_mint,
        base_vault_address: base_vault,
        quote_vault_address: quote_vault,
        base_amount_raw: event_amounts.0.to_string(),
        quote_amount_raw: event_amounts.1.to_string(),
        trade_side: crate::SwapTradeSide::SellBase,
    }));
}

/// Infers swap amounts from flattened CPI token transfers for one Meteora instruction.
pub(crate) fn infer_meteora_swap_amounts_from_inner_transfers(
    transaction: &crate::ChainTransactionDto,
    instructions: &[crate::ChainInstructionDto],
    decoded_instruction: &crate::ChainInstructionDto,
    pool_account: std::option::Option<&str>,
) -> Result<
    std::option::Option<crate::meteora_swap_amount_inference::MeteoraSwapAmountInference>,
    crate::Error,
> {
    let pool_account = match pool_account {
        Some(pool_account) => pool_account.trim(),
        None => return Ok(None),
    };
    if pool_account.is_empty() {
        return Ok(None);
    }
    let transaction_value_result =
        serde_json::from_str::<serde_json::Value>(transaction.transaction_json.as_str());
    let transaction_value = match transaction_value_result {
        Ok(transaction_value) => transaction_value,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse transaction_json for Meteora swap amount inference: {}",
                error
            )));
        },
    };
    let meta_value = match transaction.meta_json.as_deref() {
        Some(meta_json) => {
            let meta_value_result = serde_json::from_str::<serde_json::Value>(meta_json);
            match meta_value_result {
                Ok(meta_value) => meta_value,
                Err(error) => {
                    return Err(crate::Error::Json(format!(
                        "cannot parse meta_json for Meteora swap amount inference: {}",
                        error
                    )));
                },
            }
        },
        None => match transaction_value.get("meta") {
            Some(meta) => meta.clone(),
            None => return Ok(None),
        },
    };
    if crate::meteora_swap_amount_inference::transaction_failed(&meta_value) {
        return Ok(None);
    }
    let account_keys = crate::meteora_swap_amount_inference::collect_transaction_account_keys(
        &transaction_value,
        &meta_value,
    );
    let token_accounts = crate::meteora_swap_amount_inference::collect_token_account_infos(
        &meta_value,
        account_keys.as_slice(),
    );
    let transfers_result =
        crate::meteora_swap_amount_inference::collect_instruction_window_transfers(
            instructions,
            decoded_instruction,
            token_accounts.as_slice(),
        );
    let transfers = match transfers_result {
        Ok(transfers) => transfers,
        Err(error) => return Err(error),
    };
    if transfers.len() < 2 {
        return Ok(None);
    }
    let mut pool_touched = std::vec::Vec::new();
    for transfer in transfers {
        let touched =
            crate::meteora_swap_amount_inference::pool_touched_transfer(transfer, pool_account);
        if let Some(touched) = touched {
            pool_touched.push(touched);
        }
    }
    if pool_touched.len() < 2 {
        return Ok(None);
    }
    let inference = crate::meteora_swap_amount_inference::build_inference_from_pool_transfers(
        pool_touched.as_slice(),
    );
    return Ok(inference);
}

fn build_inference_from_pool_transfers(
    transfers: &[crate::meteora_swap_amount_inference::PoolTouchedTransfer],
) -> std::option::Option<crate::meteora_swap_amount_inference::MeteoraSwapAmountInference> {
    let first =
        crate::meteora_swap_amount_inference::first_non_zero_transfer_for_new_mint(transfers, None);
    let first = match first {
        Some(first) => first,
        None => return None,
    };
    let second = crate::meteora_swap_amount_inference::first_non_zero_transfer_for_new_mint(
        transfers,
        Some(first.mint.as_str()),
    );
    let second = match second {
        Some(second) => second,
        None => return None,
    };
    let first_is_quote =
        crate::meteora_swap_amount_inference::is_known_quote_mint(first.mint.as_str());
    let second_is_quote =
        crate::meteora_swap_amount_inference::is_known_quote_mint(second.mint.as_str());
    let (base_transfer, quote_transfer) = if first_is_quote && !second_is_quote {
        (second, first)
    } else {
        (first, second)
    };
    let trade_side = crate::meteora_swap_amount_inference::infer_trade_side(
        base_transfer.direction,
        quote_transfer.direction,
    );
    return Some(crate::meteora_swap_amount_inference::MeteoraSwapAmountInference {
        base_token_mint: base_transfer.mint.clone(),
        quote_token_mint: quote_transfer.mint.clone(),
        base_vault_address: base_transfer.vault_address.clone(),
        quote_vault_address: quote_transfer.vault_address.clone(),
        base_amount_raw: base_transfer.amount_raw.clone(),
        quote_amount_raw: quote_transfer.amount_raw.clone(),
        trade_side,
    });
}

fn first_non_zero_transfer_for_new_mint<'a>(
    transfers: &'a [crate::meteora_swap_amount_inference::PoolTouchedTransfer],
    existing_mint: std::option::Option<&str>,
) -> std::option::Option<&'a crate::meteora_swap_amount_inference::PoolTouchedTransfer> {
    for transfer in transfers {
        if !crate::meteora_swap_amount_inference::is_non_zero_amount(transfer.amount_raw.as_str()) {
            continue;
        }
        if let Some(existing_mint) = existing_mint {
            if transfer.mint.as_str() == existing_mint {
                continue;
            }
        }
        return Some(transfer);
    }
    return None;
}

fn infer_trade_side(
    base_direction: crate::meteora_swap_amount_inference::PoolTransferDirection,
    quote_direction: crate::meteora_swap_amount_inference::PoolTransferDirection,
) -> crate::SwapTradeSide {
    match (base_direction, quote_direction) {
        (
            crate::meteora_swap_amount_inference::PoolTransferDirection::OutOfPool,
            crate::meteora_swap_amount_inference::PoolTransferDirection::IntoPool,
        ) => return crate::SwapTradeSide::BuyBase,
        (
            crate::meteora_swap_amount_inference::PoolTransferDirection::IntoPool,
            crate::meteora_swap_amount_inference::PoolTransferDirection::OutOfPool,
        ) => return crate::SwapTradeSide::SellBase,
        _ => return crate::SwapTradeSide::Unknown,
    }
}

fn pool_touched_transfer(
    transfer: crate::meteora_swap_amount_inference::TokenTransferInfo,
    pool_account: &str,
) -> std::option::Option<crate::meteora_swap_amount_inference::PoolTouchedTransfer> {
    let source_owned_by_pool = crate::meteora_swap_amount_inference::option_string_equals(
        transfer.source_owner.as_deref(),
        pool_account,
    );
    let destination_owned_by_pool = crate::meteora_swap_amount_inference::option_string_equals(
        transfer.destination_owner.as_deref(),
        pool_account,
    );
    if !source_owned_by_pool && !destination_owned_by_pool {
        return None;
    }
    if destination_owned_by_pool {
        return Some(crate::meteora_swap_amount_inference::PoolTouchedTransfer {
            mint: transfer.mint,
            amount_raw: transfer.amount_raw,
            vault_address: Some(transfer.destination),
            direction: crate::meteora_swap_amount_inference::PoolTransferDirection::IntoPool,
        });
    }
    return Some(crate::meteora_swap_amount_inference::PoolTouchedTransfer {
        mint: transfer.mint,
        amount_raw: transfer.amount_raw,
        vault_address: Some(transfer.source),
        direction: crate::meteora_swap_amount_inference::PoolTransferDirection::OutOfPool,
    });
}

fn collect_instruction_window_transfers(
    instructions: &[crate::ChainInstructionDto],
    decoded_instruction: &crate::ChainInstructionDto,
    token_accounts: &[crate::meteora_swap_amount_inference::TokenAccountInfo],
) -> Result<std::vec::Vec<crate::meteora_swap_amount_inference::TokenTransferInfo>, crate::Error> {
    let mut transfers = std::vec::Vec::new();
    let stop_inner_instruction_index =
        crate::meteora_swap_amount_inference::find_window_stop_inner_instruction_index(
            instructions,
            decoded_instruction,
        );
    for instruction in instructions {
        if !crate::meteora_swap_amount_inference::instruction_inside_window(
            instruction,
            decoded_instruction,
            stop_inner_instruction_index,
        ) {
            continue;
        }
        let transfer_result =
            crate::meteora_swap_amount_inference::parse_token_transfer_instruction(
                instruction,
                token_accounts,
            );
        let transfer = match transfer_result {
            Ok(transfer) => transfer,
            Err(error) => return Err(error),
        };
        if let Some(transfer) = transfer {
            transfers.push(transfer);
        }
    }
    return Ok(transfers);
}

fn instruction_inside_window(
    candidate: &crate::ChainInstructionDto,
    decoded_instruction: &crate::ChainInstructionDto,
    stop_inner_instruction_index: std::option::Option<u32>,
) -> bool {
    if candidate.transaction_id != decoded_instruction.transaction_id {
        return false;
    }
    if candidate.instruction_index != decoded_instruction.instruction_index {
        return false;
    }
    let candidate_inner_instruction_index = match candidate.inner_instruction_index {
        Some(candidate_inner_instruction_index) => candidate_inner_instruction_index,
        None => return false,
    };
    if let Some(decoded_inner_instruction_index) = decoded_instruction.inner_instruction_index {
        if candidate_inner_instruction_index <= decoded_inner_instruction_index {
            return false;
        }
    }
    if let Some(stop_inner_instruction_index) = stop_inner_instruction_index {
        if candidate_inner_instruction_index >= stop_inner_instruction_index {
            return false;
        }
    }
    return true;
}

fn find_window_stop_inner_instruction_index(
    instructions: &[crate::ChainInstructionDto],
    decoded_instruction: &crate::ChainInstructionDto,
) -> std::option::Option<u32> {
    let decoded_inner_instruction_index = match decoded_instruction.inner_instruction_index {
        Some(decoded_inner_instruction_index) => decoded_inner_instruction_index,
        None => return None,
    };
    let decoded_stack_height = match decoded_instruction.stack_height {
        Some(decoded_stack_height) => decoded_stack_height,
        None => return None,
    };
    let mut stop_inner_instruction_index = None;
    for instruction in instructions {
        if instruction.transaction_id != decoded_instruction.transaction_id {
            continue;
        }
        if instruction.instruction_index != decoded_instruction.instruction_index {
            continue;
        }
        let candidate_inner_instruction_index = match instruction.inner_instruction_index {
            Some(candidate_inner_instruction_index) => candidate_inner_instruction_index,
            None => continue,
        };
        if candidate_inner_instruction_index <= decoded_inner_instruction_index {
            continue;
        }
        let candidate_stack_height = match instruction.stack_height {
            Some(candidate_stack_height) => candidate_stack_height,
            None => continue,
        };
        if candidate_stack_height > decoded_stack_height {
            continue;
        }
        match stop_inner_instruction_index {
            Some(current_stop) => {
                if candidate_inner_instruction_index < current_stop {
                    stop_inner_instruction_index = Some(candidate_inner_instruction_index);
                }
            },
            None => stop_inner_instruction_index = Some(candidate_inner_instruction_index),
        }
    }
    return stop_inner_instruction_index;
}

fn parse_token_transfer_instruction(
    instruction: &crate::ChainInstructionDto,
    token_accounts: &[crate::meteora_swap_amount_inference::TokenAccountInfo],
) -> Result<
    std::option::Option<crate::meteora_swap_amount_inference::TokenTransferInfo>,
    crate::Error,
> {
    let parsed_json_text = match instruction.parsed_json.as_deref() {
        Some(parsed_json_text) => parsed_json_text,
        None => return Ok(None),
    };
    let parsed_json_result = serde_json::from_str::<serde_json::Value>(parsed_json_text);
    let parsed_json = match parsed_json_result {
        Ok(parsed_json) => parsed_json,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse parsed_json for Meteora token transfer inference: {}",
                error
            )));
        },
    };
    let parsed_type = crate::meteora_swap_amount_inference::parsed_instruction_type(
        instruction.parsed_type.as_deref(),
        &parsed_json,
    );
    let parsed_type = match parsed_type {
        Some(parsed_type) => parsed_type,
        None => return Ok(None),
    };
    if parsed_type != "transfer" && parsed_type != "transferChecked" {
        return Ok(None);
    }
    let info = match crate::meteora_swap_amount_inference::parsed_instruction_info(&parsed_json) {
        Some(info) => info,
        None => return Ok(None),
    };
    let source = match crate::meteora_swap_amount_inference::extract_string_by_candidate_keys(
        info,
        &["source"],
    ) {
        Some(source) => source,
        None => return Ok(None),
    };
    let destination = match crate::meteora_swap_amount_inference::extract_string_by_candidate_keys(
        info,
        &["destination"],
    ) {
        Some(destination) => destination,
        None => return Ok(None),
    };
    let amount_raw = match crate::meteora_swap_amount_inference::extract_amount_string(info) {
        Some(amount_raw) => amount_raw,
        None => return Ok(None),
    };
    let mint = match crate::meteora_swap_amount_inference::extract_string_by_candidate_keys(
        info,
        &["mint"],
    ) {
        Some(mint) => mint,
        None => match crate::meteora_swap_amount_inference::mint_for_token_account(
            token_accounts,
            source.as_str(),
        ) {
            Some(mint) => mint,
            None => match crate::meteora_swap_amount_inference::mint_for_token_account(
                token_accounts,
                destination.as_str(),
            ) {
                Some(mint) => mint,
                None => return Ok(None),
            },
        },
    };
    let source_owner = crate::meteora_swap_amount_inference::owner_for_token_account(
        token_accounts,
        source.as_str(),
    );
    let destination_owner = crate::meteora_swap_amount_inference::owner_for_token_account(
        token_accounts,
        destination.as_str(),
    );
    return Ok(Some(crate::meteora_swap_amount_inference::TokenTransferInfo {
        mint,
        amount_raw,
        source,
        destination,
        source_owner,
        destination_owner,
    }));
}

fn parsed_instruction_type(
    parsed_type: std::option::Option<&str>,
    parsed_json: &serde_json::Value,
) -> std::option::Option<std::string::String> {
    if let Some(parsed_type) = parsed_type {
        if !parsed_type.trim().is_empty() {
            return Some(parsed_type.to_string());
        }
    }
    if let Some(parsed_type) = parsed_json.get("type").and_then(|value| return value.as_str()) {
        return Some(parsed_type.to_string());
    }
    let nested_type = parsed_json
        .get("parsed")
        .and_then(|value| return value.get("type"))
        .and_then(|value| return value.as_str());
    match nested_type {
        Some(nested_type) => return Some(nested_type.to_string()),
        None => return None,
    }
}

fn parsed_instruction_info(
    parsed_json: &serde_json::Value,
) -> std::option::Option<&serde_json::Value> {
    if let Some(info) = parsed_json.get("info") {
        return Some(info);
    }
    return parsed_json.get("parsed").and_then(|value| return value.get("info"));
}

fn collect_token_account_infos(
    meta: &serde_json::Value,
    account_keys: &[std::string::String],
) -> std::vec::Vec<crate::meteora_swap_amount_inference::TokenAccountInfo> {
    let mut accounts = std::vec::Vec::new();
    crate::meteora_swap_amount_inference::collect_token_account_side(
        meta,
        account_keys,
        "preTokenBalances",
        &mut accounts,
    );
    crate::meteora_swap_amount_inference::collect_token_account_side(
        meta,
        account_keys,
        "postTokenBalances",
        &mut accounts,
    );
    return accounts;
}

fn collect_token_account_side(
    meta: &serde_json::Value,
    account_keys: &[std::string::String],
    key: &str,
    accounts: &mut std::vec::Vec<crate::meteora_swap_amount_inference::TokenAccountInfo>,
) {
    let balances = match meta.get(key).and_then(|value| return value.as_array()) {
        Some(balances) => balances,
        None => return,
    };
    for balance in balances {
        let account_index =
            match balance.get("accountIndex").and_then(|value| return value.as_u64()) {
                Some(account_index) => account_index,
                None => continue,
            };
        let account_index = match usize::try_from(account_index) {
            Ok(account_index) => account_index,
            Err(_) => continue,
        };
        let account_address = match account_keys.get(account_index) {
            Some(account_address) => account_address.clone(),
            None => continue,
        };
        let mint = match balance.get("mint").and_then(|value| return value.as_str()) {
            Some(mint) => mint.to_string(),
            None => continue,
        };
        let owner = balance
            .get("owner")
            .and_then(|value| return value.as_str())
            .map(|text| return text.to_string());
        if crate::meteora_swap_amount_inference::token_account_known(
            accounts.as_slice(),
            account_address.as_str(),
        ) {
            continue;
        }
        accounts.push(crate::meteora_swap_amount_inference::TokenAccountInfo {
            account_address,
            mint,
            owner,
        });
    }
}

fn token_account_known(
    accounts: &[crate::meteora_swap_amount_inference::TokenAccountInfo],
    account_address: &str,
) -> bool {
    for account in accounts {
        if account.account_address.as_str() == account_address {
            return true;
        }
    }
    return false;
}

fn mint_for_token_account(
    accounts: &[crate::meteora_swap_amount_inference::TokenAccountInfo],
    account_address: &str,
) -> std::option::Option<std::string::String> {
    for account in accounts {
        if account.account_address.as_str() == account_address {
            return Some(account.mint.clone());
        }
    }
    return None;
}

fn owner_for_token_account(
    accounts: &[crate::meteora_swap_amount_inference::TokenAccountInfo],
    account_address: &str,
) -> std::option::Option<std::string::String> {
    for account in accounts {
        if account.account_address.as_str() == account_address {
            return account.owner.clone();
        }
    }
    return None;
}

fn collect_transaction_account_keys(
    transaction: &serde_json::Value,
    meta: &serde_json::Value,
) -> std::vec::Vec<std::string::String> {
    let mut account_keys = std::vec::Vec::new();
    crate::meteora_swap_amount_inference::collect_account_keys_from_path(
        transaction,
        &["transaction", "message", "accountKeys"],
        &mut account_keys,
    );
    if account_keys.is_empty() {
        crate::meteora_swap_amount_inference::collect_account_keys_from_path(
            transaction,
            &["message", "accountKeys"],
            &mut account_keys,
        );
    }
    crate::meteora_swap_amount_inference::collect_loaded_addresses(meta, &mut account_keys);
    return account_keys;
}

fn collect_account_keys_from_path(
    value: &serde_json::Value,
    path: &[&str],
    output: &mut std::vec::Vec<std::string::String>,
) {
    let mut current = value;
    for key in path {
        current = match current.get(*key) {
            Some(current) => current,
            None => return,
        };
    }
    let account_keys = match current.as_array() {
        Some(account_keys) => account_keys,
        None => return,
    };
    for account_key in account_keys {
        if let Some(text) = account_key.as_str() {
            output.push(text.to_string());
            continue;
        }
        let pubkey = account_key.get("pubkey").and_then(|value| return value.as_str());
        if let Some(pubkey) = pubkey {
            output.push(pubkey.to_string());
        }
    }
}

fn collect_loaded_addresses(
    meta: &serde_json::Value,
    output: &mut std::vec::Vec<std::string::String>,
) {
    let loaded_addresses = match meta.get("loadedAddresses") {
        Some(loaded_addresses) => loaded_addresses,
        None => return,
    };
    crate::meteora_swap_amount_inference::collect_loaded_address_side(
        loaded_addresses,
        "writable",
        output,
    );
    crate::meteora_swap_amount_inference::collect_loaded_address_side(
        loaded_addresses,
        "readonly",
        output,
    );
}

fn collect_loaded_address_side(
    loaded_addresses: &serde_json::Value,
    key: &str,
    output: &mut std::vec::Vec<std::string::String>,
) {
    let values = match loaded_addresses.get(key).and_then(|value| return value.as_array()) {
        Some(values) => values,
        None => return,
    };
    for value in values {
        if let Some(text) = value.as_str() {
            output.push(text.to_string());
        }
    }
}

fn extract_amount_string(value: &serde_json::Value) -> std::option::Option<std::string::String> {
    let direct = crate::meteora_swap_amount_inference::extract_scalar_as_string_by_candidate_keys(
        value,
        &["amount"],
    );
    if direct.is_some() {
        return direct;
    }
    let token_amount = match value.get("tokenAmount") {
        Some(token_amount) => token_amount,
        None => return None,
    };
    return crate::meteora_swap_amount_inference::extract_scalar_as_string_by_candidate_keys(
        token_amount,
        &["amount"],
    );
}

fn extract_string_by_candidate_keys(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    if let Some(object) = value.as_object() {
        for key in candidate_keys {
            if let Some(candidate) = object.get(*key) {
                if let Some(text) = candidate.as_str() {
                    return Some(text.to_string());
                }
            }
        }
    }
    return None;
}

fn extract_scalar_as_string_by_candidate_keys(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    if let Some(object) = value.as_object() {
        for key in candidate_keys {
            let candidate = match object.get(*key) {
                Some(candidate) => candidate,
                None => continue,
            };
            if let Some(text) = candidate.as_str() {
                return Some(text.to_string());
            }
            if let Some(number) = candidate.as_u64() {
                return Some(number.to_string());
            }
            if let Some(number) = candidate.as_i64() {
                return Some(number.to_string());
            }
        }
    }
    return None;
}

fn account_at(
    accounts: &[std::string::String],
    index: usize,
) -> std::option::Option<std::string::String> {
    return accounts.get(index).map(|value| return value.clone());
}

fn extract_damm_v1_event_amounts_for_pool(
    log_messages: &[std::string::String],
    pool_account: &str,
) -> std::option::Option<(u64, u64)> {
    let mut matched_pool_for_next_damm_v1_invoke = false;
    let mut inside_matched_damm_v1 = false;
    let mut matched_events = std::vec::Vec::new();
    let mut fallback_events = std::vec::Vec::new();
    for log_message in log_messages {
        if crate::meteora_swap_amount_inference::log_message_is_exact_program_log(
            log_message.as_str(),
            pool_account,
        ) {
            matched_pool_for_next_damm_v1_invoke = true;
            continue;
        }
        if crate::meteora_swap_amount_inference::is_damm_v1_program_invoke(log_message.as_str()) {
            inside_matched_damm_v1 = matched_pool_for_next_damm_v1_invoke;
            matched_pool_for_next_damm_v1_invoke = false;
            continue;
        }
        if crate::meteora_swap_amount_inference::is_damm_v1_program_success(log_message.as_str()) {
            inside_matched_damm_v1 = false;
            continue;
        }
        let event_amounts = crate::meteora_swap_amount_inference::decode_damm_v1_swap_event_log(
            log_message.as_str(),
        );
        if let Some(event_amounts) = event_amounts {
            if inside_matched_damm_v1 {
                matched_events.push(event_amounts);
            }
            fallback_events.push(event_amounts);
        }
    }
    if matched_events.len() == 1 {
        return matched_events.first().copied();
    }
    if matched_events.len() > 1 {
        return None;
    }
    if fallback_events.len() == 1 {
        return fallback_events.first().copied();
    }
    return None;
}

fn log_message_is_exact_program_log(log_message: &str, value: &str) -> bool {
    let expected = format!("Program log: {}", value.trim());
    return log_message.trim() == expected.as_str();
}

fn is_damm_v1_program_invoke(log_message: &str) -> bool {
    let prefix = format!("Program {} invoke", crate::METEORA_DAMM_V1_PROGRAM_ID);
    return log_message.trim().starts_with(prefix.as_str());
}

fn is_damm_v1_program_success(log_message: &str) -> bool {
    let expected = format!("Program {} success", crate::METEORA_DAMM_V1_PROGRAM_ID);
    return log_message.trim() == expected.as_str();
}

fn decode_damm_v1_swap_event_log(log_message: &str) -> std::option::Option<(u64, u64)> {
    let prefix = "Program data: ";
    let trimmed = log_message.trim();
    if !trimmed.starts_with(prefix) {
        return None;
    }
    let encoded = trimmed.trim_start_matches(prefix).trim();
    let decoded = crate::meteora_swap_amount_inference::decode_base64_payload(encoded);
    let decoded = match decoded {
        Some(decoded) => decoded,
        None => return None,
    };
    if decoded.len() < 24 {
        return None;
    }
    let discriminator = &decoded[0..8];
    if discriminator != [0x51, 0x6c, 0xe3, 0xbe, 0xcd, 0xd0, 0x0a, 0xc4] {
        return None;
    }
    let amount_in = match crate::meteora_swap_amount_inference::read_u64_le(decoded.as_slice(), 8) {
        Some(amount_in) => amount_in,
        None => return None,
    };
    let amount_out = match crate::meteora_swap_amount_inference::read_u64_le(decoded.as_slice(), 16)
    {
        Some(amount_out) => amount_out,
        None => return None,
    };
    return Some((amount_in, amount_out));
}

fn decode_base64_payload(encoded: &str) -> std::option::Option<std::vec::Vec<u8>> {
    use base64::Engine as _;

    let decoded = base64::engine::general_purpose::STANDARD.decode(encoded.as_bytes());
    match decoded {
        Ok(decoded) => return Some(decoded),
        Err(_) => return None,
    }
}

fn read_u64_le(bytes: &[u8], offset: usize) -> std::option::Option<u64> {
    if bytes.len() < offset + 8 {
        return None;
    }
    let mut value = 0_u64;
    let mut shift = 0_u32;
    let end = offset + 8;
    let mut index = offset;
    while index < end {
        value |= u64::from(bytes[index]) << shift;
        shift += 8;
        index += 1;
    }
    return Some(value);
}

fn is_known_quote_mint(mint: &str) -> bool {
    return mint == crate::WSOL_MINT_ID
        || mint == crate::USDC_MINT_ID
        || mint == crate::USDT_MINT_ID;
}

fn option_string_equals(left: std::option::Option<&str>, right: &str) -> bool {
    let left = match left {
        Some(left) => left.trim(),
        None => return false,
    };
    return left == right.trim();
}

fn is_non_zero_amount(amount: &str) -> bool {
    let normalized = amount.trim();
    if normalized.is_empty() {
        return false;
    }
    let parsed = normalized.parse::<u128>();
    match parsed {
        Ok(value) => return value > 0,
        Err(_) => return false,
    }
}

fn transaction_failed(meta: &serde_json::Value) -> bool {
    if let Some(err) = meta.get("err") {
        if !err.is_null() {
            return true;
        }
    }
    if let Some(status) = meta.get("status") {
        if let Some(object) = status.as_object() {
            if object.get("Err").is_some() {
                return true;
            }
        }
    }
    return false;
}
