pub use solana_account_decoder_client_types::token::{
    UiConfidentialMintBurn, UiConfidentialTransferAccount, UiConfidentialTransferFeeAmount,
    UiConfidentialTransferFeeConfig, UiConfidentialTransferMint, UiCpiGuard, UiDefaultAccountState,
    UiExtension, UiGroupMemberPointer, UiGroupPointer, UiInterestBearingConfig, UiMemoTransfer,
    UiMetadataPointer, UiMintCloseAuthority, UiPausableConfig, UiPermanentDelegate,
    UiScaledUiAmountConfig, UiTokenGroup, UiTokenGroupMember, UiTokenMetadata, UiTransferFee,
    UiTransferFeeAmount, UiTransferFeeConfig, UiTransferHook, UiTransferHookAccount,
};
use {
    crate::parse_token::convert_account_state,
    solana_clock::UnixTimestamp,
    solana_program_pack::Pack,
    solana_pubkey::Pubkey,
    spl_token_2022_interface::{
        extension::{self, BaseState, BaseStateWithExtensions, ExtensionType, StateWithExtensions},
        solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey,
    },
    spl_token_group_interface::state::{TokenGroup, TokenGroupMember},
    spl_token_metadata_interface::state::TokenMetadata,
};

pub fn parse_extension<S: BaseState + Pack>(
    extension_type: &ExtensionType,
    account: &StateWithExtensions<S>,
) -> UiExtension {
    match extension_type {
        ExtensionType::Uninitialized => UiExtension::Uninitialized,
        ExtensionType::TransferFeeConfig => account
            .get_extension::<extension::transfer_fee::TransferFeeConfig>()
            .map(|&extension| {
                UiExtension::TransferFeeConfig(convert_transfer_fee_config(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::TransferFeeAmount => account
            .get_extension::<extension::transfer_fee::TransferFeeAmount>()
            .map(|&extension| {
                UiExtension::TransferFeeAmount(convert_transfer_fee_amount(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::MintCloseAuthority => account
            .get_extension::<extension::mint_close_authority::MintCloseAuthority>()
            .map(|&extension| {
                UiExtension::MintCloseAuthority(convert_mint_close_authority(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::ConfidentialTransferMint => account
            .get_extension::<extension::confidential_transfer::ConfidentialTransferMint>()
            .map(|&extension| {
                UiExtension::ConfidentialTransferMint(convert_confidential_transfer_mint(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::ConfidentialTransferFeeConfig => account
            .get_extension::<extension::confidential_transfer_fee::ConfidentialTransferFeeConfig>()
            .map(|&extension| {
                UiExtension::ConfidentialTransferFeeConfig(
                    convert_confidential_transfer_fee_config(extension),
                )
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::ConfidentialTransferAccount => account
            .get_extension::<extension::confidential_transfer::ConfidentialTransferAccount>()
            .map(|&extension| {
                UiExtension::ConfidentialTransferAccount(convert_confidential_transfer_account(
                    extension,
                ))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::ConfidentialTransferFeeAmount => account
            .get_extension::<extension::confidential_transfer_fee::ConfidentialTransferFeeAmount>()
            .map(|&extension| {
                UiExtension::ConfidentialTransferFeeAmount(
                    convert_confidential_transfer_fee_amount(extension),
                )
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::DefaultAccountState => account
            .get_extension::<extension::default_account_state::DefaultAccountState>()
            .map(|&extension| {
                UiExtension::DefaultAccountState(convert_default_account_state(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::ImmutableOwner => UiExtension::ImmutableOwner,
        ExtensionType::MemoTransfer => account
            .get_extension::<extension::memo_transfer::MemoTransfer>()
            .map(|&extension| UiExtension::MemoTransfer(convert_memo_transfer(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::NonTransferable => UiExtension::NonTransferable,
        ExtensionType::InterestBearingConfig => account
            .get_extension::<extension::interest_bearing_mint::InterestBearingConfig>()
            .map(|&extension| {
                UiExtension::InterestBearingConfig(convert_interest_bearing_config(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::CpiGuard => account
            .get_extension::<extension::cpi_guard::CpiGuard>()
            .map(|&extension| UiExtension::CpiGuard(convert_cpi_guard(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::PermanentDelegate => account
            .get_extension::<extension::permanent_delegate::PermanentDelegate>()
            .map(|&extension| UiExtension::PermanentDelegate(convert_permanent_delegate(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::NonTransferableAccount => UiExtension::NonTransferableAccount,
        ExtensionType::MetadataPointer => account
            .get_extension::<extension::metadata_pointer::MetadataPointer>()
            .map(|&extension| UiExtension::MetadataPointer(convert_metadata_pointer(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::TokenMetadata => account
            .get_variable_len_extension::<TokenMetadata>()
            .map(|extension| UiExtension::TokenMetadata(convert_token_metadata(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::TransferHook => account
            .get_extension::<extension::transfer_hook::TransferHook>()
            .map(|&extension| UiExtension::TransferHook(convert_transfer_hook(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::TransferHookAccount => account
            .get_extension::<extension::transfer_hook::TransferHookAccount>()
            .map(|&extension| {
                UiExtension::TransferHookAccount(convert_transfer_hook_account(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::GroupPointer => account
            .get_extension::<extension::group_pointer::GroupPointer>()
            .map(|&extension| UiExtension::GroupPointer(convert_group_pointer(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::GroupMemberPointer => account
            .get_extension::<extension::group_member_pointer::GroupMemberPointer>()
            .map(|&extension| {
                UiExtension::GroupMemberPointer(convert_group_member_pointer(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::TokenGroup => account
            .get_extension::<TokenGroup>()
            .map(|&extension| UiExtension::TokenGroup(convert_token_group(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::TokenGroupMember => account
            .get_extension::<TokenGroupMember>()
            .map(|&extension| UiExtension::TokenGroupMember(convert_token_group_member(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::ConfidentialMintBurn => account
            .get_extension::<extension::confidential_mint_burn::ConfidentialMintBurn>()
            .map(|&extension| {
                UiExtension::ConfidentialMintBurn(convert_confidential_mint_burn(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::ScaledUiAmount => account
            .get_extension::<extension::scaled_ui_amount::ScaledUiAmountConfig>()
            .map(|&extension| {
                UiExtension::ScaledUiAmountConfig(convert_scaled_ui_amount(extension))
            })
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::Pausable => account
            .get_extension::<extension::pausable::PausableConfig>()
            .map(|&extension| UiExtension::PausableConfig(convert_pausable_config(extension)))
            .unwrap_or(UiExtension::UnparseableExtension),
        ExtensionType::PausableAccount => UiExtension::PausableAccount,
    }
}

fn convert_transfer_fee(transfer_fee: extension::transfer_fee::TransferFee) -> UiTransferFee {
    UiTransferFee {
        epoch: u64::from(transfer_fee.epoch),
        maximum_fee: u64::from(transfer_fee.maximum_fee),
        transfer_fee_basis_points: u16::from(transfer_fee.transfer_fee_basis_points),
    }
}

fn convert_transfer_fee_config(
    transfer_fee_config: extension::transfer_fee::TransferFeeConfig,
) -> UiTransferFeeConfig {
    let transfer_fee_config_authority: Option<Pubkey> =
        transfer_fee_config.transfer_fee_config_authority.into();
    let withdraw_withheld_authority: Option<Pubkey> =
        transfer_fee_config.withdraw_withheld_authority.into();

    UiTransferFeeConfig {
        transfer_fee_config_authority: transfer_fee_config_authority
            .map(|pubkey| pubkey.to_string()),
        withdraw_withheld_authority: withdraw_withheld_authority.map(|pubkey| pubkey.to_string()),
        withheld_amount: u64::from(transfer_fee_config.withheld_amount),
        older_transfer_fee: convert_transfer_fee(transfer_fee_config.older_transfer_fee),
        newer_transfer_fee: convert_transfer_fee(transfer_fee_config.newer_transfer_fee),
    }
}

fn convert_transfer_fee_amount(
    transfer_fee_amount: extension::transfer_fee::TransferFeeAmount,
) -> UiTransferFeeAmount {
    UiTransferFeeAmount {
        withheld_amount: u64::from(transfer_fee_amount.withheld_amount),
    }
}

fn convert_mint_close_authority(
    mint_close_authority: extension::mint_close_authority::MintCloseAuthority,
) -> UiMintCloseAuthority {
    let authority: Option<Pubkey> = mint_close_authority.close_authority.into();
    UiMintCloseAuthority {
        close_authority: authority.map(|pubkey| pubkey.to_string()),
    }
}

fn convert_default_account_state(
    default_account_state: extension::default_account_state::DefaultAccountState,
) -> UiDefaultAccountState {
    let account_state =
        spl_token_2022_interface::state::AccountState::try_from(default_account_state.state)
            .unwrap_or_default();
    UiDefaultAccountState {
        account_state: convert_account_state(account_state),
    }
}

fn convert_memo_transfer(memo_transfer: extension::memo_transfer::MemoTransfer) -> UiMemoTransfer {
    UiMemoTransfer {
        require_incoming_transfer_memos: memo_transfer.require_incoming_transfer_memos.into(),
    }
}

fn convert_interest_bearing_config(
    interest_bearing_config: extension::interest_bearing_mint::InterestBearingConfig,
) -> UiInterestBearingConfig {
    let rate_authority: Option<Pubkey> = interest_bearing_config.rate_authority.into();

    UiInterestBearingConfig {
        rate_authority: rate_authority.map(|pubkey| pubkey.to_string()),
        initialization_timestamp: UnixTimestamp::from(
            interest_bearing_config.initialization_timestamp,
        ),
        pre_update_average_rate: i16::from(interest_bearing_config.pre_update_average_rate),
        last_update_timestamp: UnixTimestamp::from(interest_bearing_config.last_update_timestamp),
        current_rate: i16::from(interest_bearing_config.current_rate),
    }
}

fn convert_cpi_guard(cpi_guard: extension::cpi_guard::CpiGuard) -> UiCpiGuard {
    UiCpiGuard {
        lock_cpi: cpi_guard.lock_cpi.into(),
    }
}

fn convert_permanent_delegate(
    permanent_delegate: extension::permanent_delegate::PermanentDelegate,
) -> UiPermanentDelegate {
    let delegate: Option<Pubkey> = permanent_delegate.delegate.into();
    UiPermanentDelegate {
        delegate: delegate.map(|pubkey| pubkey.to_string()),
    }
}

pub fn convert_confidential_transfer_mint(
    confidential_transfer_mint: extension::confidential_transfer::ConfidentialTransferMint,
) -> UiConfidentialTransferMint {
    let authority: Option<Pubkey> = confidential_transfer_mint.authority.into();
    let auditor_elgamal_pubkey: Option<PodElGamalPubkey> =
        confidential_transfer_mint.auditor_elgamal_pubkey.into();
    UiConfidentialTransferMint {
        authority: authority.map(|pubkey| pubkey.to_string()),
        auto_approve_new_accounts: confidential_transfer_mint.auto_approve_new_accounts.into(),
        auditor_elgamal_pubkey: auditor_elgamal_pubkey.map(|pubkey| pubkey.to_string()),
    }
}

pub fn convert_confidential_transfer_fee_config(
    confidential_transfer_fee_config: extension::confidential_transfer_fee::ConfidentialTransferFeeConfig,
) -> UiConfidentialTransferFeeConfig {
    let authority: Option<Pubkey> = confidential_transfer_fee_config.authority.into();
    let withdraw_withheld_authority_elgamal_pubkey: Option<PodElGamalPubkey> =
        confidential_transfer_fee_config
            .withdraw_withheld_authority_elgamal_pubkey
            .into();
    UiConfidentialTransferFeeConfig {
        authority: authority.map(|pubkey| pubkey.to_string()),
        withdraw_withheld_authority_elgamal_pubkey: withdraw_withheld_authority_elgamal_pubkey
            .map(|pubkey| pubkey.to_string()),
        harvest_to_mint_enabled: confidential_transfer_fee_config
            .harvest_to_mint_enabled
            .into(),
        withheld_amount: format!("{}", confidential_transfer_fee_config.withheld_amount),
    }
}

fn convert_confidential_transfer_account(
    confidential_transfer_account: extension::confidential_transfer::ConfidentialTransferAccount,
) -> UiConfidentialTransferAccount {
    UiConfidentialTransferAccount {
        approved: confidential_transfer_account.approved.into(),
        elgamal_pubkey: format!("{}", confidential_transfer_account.elgamal_pubkey),
        pending_balance_lo: format!("{}", confidential_transfer_account.pending_balance_lo),
        pending_balance_hi: format!("{}", confidential_transfer_account.pending_balance_hi),
        available_balance: format!("{}", confidential_transfer_account.available_balance),
        decryptable_available_balance: format!(
            "{}",
            confidential_transfer_account.decryptable_available_balance
        ),
        allow_confidential_credits: confidential_transfer_account
            .allow_confidential_credits
            .into(),
        allow_non_confidential_credits: confidential_transfer_account
            .allow_non_confidential_credits
            .into(),
        pending_balance_credit_counter: confidential_transfer_account
            .pending_balance_credit_counter
            .into(),
        maximum_pending_balance_credit_counter: confidential_transfer_account
            .maximum_pending_balance_credit_counter
            .into(),
        expected_pending_balance_credit_counter: confidential_transfer_account
            .expected_pending_balance_credit_counter
            .into(),
        actual_pending_balance_credit_counter: confidential_transfer_account
            .actual_pending_balance_credit_counter
            .into(),
    }
}

fn convert_confidential_transfer_fee_amount(
    confidential_transfer_fee_amount: extension::confidential_transfer_fee::ConfidentialTransferFeeAmount,
) -> UiConfidentialTransferFeeAmount {
    UiConfidentialTransferFeeAmount {
        withheld_amount: format!("{}", confidential_transfer_fee_amount.withheld_amount),
    }
}

fn convert_metadata_pointer(
    metadata_pointer: extension::metadata_pointer::MetadataPointer,
) -> UiMetadataPointer {
    let authority: Option<Pubkey> = metadata_pointer.authority.into();
    let metadata_address: Option<Pubkey> = metadata_pointer.metadata_address.into();
    UiMetadataPointer {
        authority: authority.map(|pubkey| pubkey.to_string()),
        metadata_address: metadata_address.map(|pubkey| pubkey.to_string()),
    }
}

fn convert_token_metadata(token_metadata: TokenMetadata) -> UiTokenMetadata {
    let update_authority: Option<Pubkey> = token_metadata.update_authority.into();
    UiTokenMetadata {
        update_authority: update_authority.map(|pubkey| pubkey.to_string()),
        mint: token_metadata.mint.to_string(),
        name: token_metadata.name,
        symbol: token_metadata.symbol,
        uri: token_metadata.uri,
        additional_metadata: token_metadata.additional_metadata,
    }
}

fn convert_transfer_hook(transfer_hook: extension::transfer_hook::TransferHook) -> UiTransferHook {
    let authority: Option<Pubkey> = transfer_hook.authority.into();
    let program_id: Option<Pubkey> = transfer_hook.program_id.into();
    UiTransferHook {
        authority: authority.map(|pubkey| pubkey.to_string()),
        program_id: program_id.map(|pubkey| pubkey.to_string()),
    }
}

fn convert_transfer_hook_account(
    transfer_hook: extension::transfer_hook::TransferHookAccount,
) -> UiTransferHookAccount {
    UiTransferHookAccount {
        transferring: transfer_hook.transferring.into(),
    }
}

fn convert_group_pointer(group_pointer: extension::group_pointer::GroupPointer) -> UiGroupPointer {
    let authority: Option<Pubkey> = group_pointer.authority.into();
    let group_address: Option<Pubkey> = group_pointer.group_address.into();
    UiGroupPointer {
        authority: authority.map(|pubkey| pubkey.to_string()),
        group_address: group_address.map(|pubkey| pubkey.to_string()),
    }
}

fn convert_group_member_pointer(
    member_pointer: extension::group_member_pointer::GroupMemberPointer,
) -> UiGroupMemberPointer {
    let authority: Option<Pubkey> = member_pointer.authority.into();
    let member_address: Option<Pubkey> = member_pointer.member_address.into();
    UiGroupMemberPointer {
        authority: authority.map(|pubkey| pubkey.to_string()),
        member_address: member_address.map(|pubkey| pubkey.to_string()),
    }
}

fn convert_token_group(token_group: TokenGroup) -> UiTokenGroup {
    let update_authority: Option<Pubkey> = token_group.update_authority.into();
    UiTokenGroup {
        update_authority: update_authority.map(|pubkey| pubkey.to_string()),
        mint: token_group.mint.to_string(),
        size: token_group.size.into(),
        max_size: token_group.max_size.into(),
    }
}

fn convert_token_group_member(member: TokenGroupMember) -> UiTokenGroupMember {
    UiTokenGroupMember {
        mint: member.mint.to_string(),
        group: member.group.to_string(),
        member_number: member.member_number.into(),
    }
}

fn convert_confidential_mint_burn(
    confidential_mint_burn: extension::confidential_mint_burn::ConfidentialMintBurn,
) -> UiConfidentialMintBurn {
    UiConfidentialMintBurn {
        confidential_supply: confidential_mint_burn.confidential_supply.to_string(),
        decryptable_supply: confidential_mint_burn.decryptable_supply.to_string(),
        supply_elgamal_pubkey: confidential_mint_burn.supply_elgamal_pubkey.to_string(),
        pending_burn: confidential_mint_burn.pending_burn.to_string(),
    }
}

fn convert_scaled_ui_amount(
    scaled_ui_amount_config: extension::scaled_ui_amount::ScaledUiAmountConfig,
) -> UiScaledUiAmountConfig {
    let authority: Option<Pubkey> = scaled_ui_amount_config.authority.into();
    let multiplier: f64 = scaled_ui_amount_config.multiplier.into();
    let new_multiplier_effective_timestamp: i64 = scaled_ui_amount_config
        .new_multiplier_effective_timestamp
        .into();
    let new_multiplier: f64 = scaled_ui_amount_config.new_multiplier.into();
    UiScaledUiAmountConfig {
        authority: authority.map(|pubkey| pubkey.to_string()),
        multiplier: multiplier.to_string(),
        new_multiplier_effective_timestamp,
        new_multiplier: new_multiplier.to_string(),
    }
}

fn convert_pausable_config(
    pausable_config: extension::pausable::PausableConfig,
) -> UiPausableConfig {
    let authority: Option<Pubkey> = pausable_config.authority.into();
    UiPausableConfig {
        authority: authority.map(|pubkey| pubkey.to_string()),
        paused: pausable_config.paused.into(),
    }
}
