#[cfg(not(target_os = "solana"))]
use {
    crate::error::TokenError,
    solana_sdk_ids::sysvar,
    solana_zk_elgamal_proof_interface::{
        instruction::ProofInstruction,
        proof_data::{
            BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data,
            CiphertextCiphertextEqualityProofData, CiphertextCommitmentEqualityProofData,
        },
    },
    spl_token_confidential_transfer_proof_extraction::instruction::{
        process_proof_location, ProofLocation,
    },
};
#[cfg(feature = "serde")]
use {
    crate::serialization::{
        aeciphertext_fromstr, elgamalciphertext_fromstr, elgamalpubkey_fromstr,
    },
    serde::{Deserialize, Serialize},
};
use {
    crate::{
        check_program_account,
        extension::confidential_transfer::DecryptableBalance,
        instruction::{encode_instruction, TokenInstruction},
    },
    alloc::{vec, vec::Vec},
    bytemuck::{Pod, Zeroable},
    num_enum::{IntoPrimitive, TryFromPrimitive},
    solana_address::Address,
    solana_instruction::{AccountMeta, Instruction},
    solana_program_error::ProgramError,
    solana_zk_sdk_pod::encryption::{
        auth_encryption::PodAeCiphertext,
        elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
    },
};

/// Confidential Transfer extension instructions
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum ConfidentialMintBurnInstruction {
    /// Initializes confidential mints and burns for a mint.
    ///
    /// The `ConfidentialMintBurnInstruction::InitializeMint` instruction
    /// requires no signers and MUST be included within the same Transaction
    /// as `TokenInstruction::InitializeMint`. Otherwise another party can
    /// initialize the configuration.
    ///
    /// The instruction fails if the `TokenInstruction::InitializeMint`
    /// instruction has already executed for the mint.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   0. `[writable]` The SPL Token mint.
    ///
    /// Data expected by this instruction:
    ///   `InitializeMintData`
    InitializeMint,
    /// Rotates the ElGamal pubkey used to encrypt confidential supply
    ///
    /// The pending burn amount must be zero in order for this instruction
    /// to be processed successfully.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   * Single authority
    ///   0. `[writable]` The SPL Token mint.
    ///   1. `[]` Instructions sysvar if `CiphertextCiphertextEquality` is
    ///      included in the same transaction or context state account if
    ///      `CiphertextCiphertextEquality` is pre-verified into a context state
    ///      account.
    ///   2. `[signer]` Confidential mint authority.
    ///
    ///   * Multisignature authority
    ///   0. `[writable]` The SPL Token mint.
    ///   1. `[]` Instructions sysvar if `CiphertextCiphertextEquality` is
    ///      included in the same transaction or context state account if
    ///      `CiphertextCiphertextEquality` is pre-verified into a context state
    ///      account.
    ///   2. `[]` The multisig authority account owner.
    ///   3. ..`[signer]` Required M signer accounts for the SPL Token Multisig
    ///
    /// Data expected by this instruction:
    ///   `RotateSupplyElGamalPubkeyData`
    RotateSupplyElGamalPubkey,
    /// Updates the decryptable supply of the mint
    ///
    /// Accounts expected by this instruction:
    ///
    ///   * Single authority
    ///   0. `[writable]` The SPL Token mint.
    ///   1. `[signer]` Confidential mint authority.
    ///
    ///   * Multisignature authority
    ///   0. `[writable]` The SPL Token mint.
    ///   1. `[]` The multisig authority account owner.
    ///   2. ..`[signer]` Required M signer accounts for the SPL Token Multisig
    ///
    /// Data expected by this instruction:
    ///   `UpdateDecryptableSupplyData`
    UpdateDecryptableSupply,
    /// Mints tokens to confidential balance
    ///
    /// Fails if the destination account is frozen.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   * Single authority
    ///   0. `[writable]` The SPL Token account.
    ///   1. `[writable]` The SPL Token mint.
    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
    ///      `zk_elgamal_proof` instructions are included in the same
    ///      transaction.
    ///   3. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyCiphertextCommitmentEquality` proof
    ///   4. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyBatchedGroupedCiphertext3HandlesValidity` proof
    ///   5. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyBatchedRangeProofU128`
    ///   6. `[signer]` The single account owner.
    ///
    ///   * Multisignature authority
    ///   0. `[writable]` The SPL Token mint.
    ///   1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero
    ///      supply elgamal-pubkey
    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
    ///      `zk_elgamal_proof` instructions are included in the same
    ///      transaction.
    ///   3. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyCiphertextCommitmentEquality` proof
    ///   4. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyBatchedGroupedCiphertext3HandlesValidity` proof
    ///   5. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyBatchedRangeProofU128`
    ///   6. `[]` The multisig account owner.
    ///   7. ..`[signer]` Required M signer accounts for the SPL Token Multisig
    ///
    /// Data expected by this instruction:
    ///   `MintInstructionData`
    Mint,
    /// Burn tokens from confidential balance
    ///
    /// Fails if the destination account is frozen.
    ///
    /// Accounts expected by this instruction:
    ///
    ///   * Single authority
    ///   0. `[writable]` The SPL Token account.
    ///   1. `[writable]` The SPL Token mint.
    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
    ///      `zk_elgamal_proof` instructions are included in the same
    ///      transaction.
    ///   3. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyCiphertextCommitmentEquality` proof
    ///   4. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyBatchedGroupedCiphertext3HandlesValidity` proof
    ///   5. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyBatchedRangeProofU128`
    ///   6. `[signer]` The single account owner.
    ///
    ///   * Multisignature authority
    ///   0. `[writable]` The SPL Token mint.
    ///   1. `[]` The SPL Token mint. `[writable]` if the mint has a non-zero
    ///      supply elgamal-pubkey
    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
    ///      `zk_elgamal_proof` instructions are included in the same
    ///      transaction.
    ///   3. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyCiphertextCommitmentEquality` proof
    ///   4. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyBatchedGroupedCiphertext3HandlesValidity` proof
    ///   5. `[]` (Optional) The context state account containing the
    ///      pre-verified `VerifyBatchedRangeProofU128`
    ///   6. `[]` The multisig account owner.
    ///   7. ..`[signer]` Required M signer accounts for the SPL Token Multisig
    ///
    /// Data expected by this instruction:
    ///   `BurnInstructionData`
    Burn,

    /// Applies the pending burn amount to the confidential supply
    ///
    ///   * Single authority
    ///   0. `[writable]` The SPL token mint.
    ///   1. `[signer]` The single mint authority.
    ///
    ///   * Multisignature authority
    ///   0. `[writable]` The SPL token mint.
    ///   1. `[]` The multisig account owner.
    ///   2. .. `[signer]` Required M signer accounts for the SPL Token Multisig
    ///      account.
    ApplyPendingBurn,
}

/// Data expected by `ConfidentialMintBurnInstruction::InitializeMint`
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct InitializeMintData {
    /// The ElGamal pubkey used to encrypt the confidential supply
    #[cfg_attr(feature = "serde", serde(with = "elgamalpubkey_fromstr"))]
    pub supply_elgamal_pubkey: PodElGamalPubkey,
    /// The initial 0 supply encrypted with the supply aes key
    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
    pub decryptable_supply: PodAeCiphertext,
}

/// Data expected by `ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey`
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct RotateSupplyElGamalPubkeyData {
    /// The new ElGamal pubkey for supply encryption
    #[cfg_attr(feature = "serde", serde(with = "elgamalpubkey_fromstr"))]
    pub new_supply_elgamal_pubkey: PodElGamalPubkey,
    /// The location of the
    /// `ProofInstruction::VerifyCiphertextCiphertextEquality` instruction
    /// relative to the `RotateSupplyElGamalPubkey` instruction in the transaction
    pub proof_instruction_offset: i8,
}

/// Data expected by `ConfidentialMintBurnInstruction::UpdateDecryptableSupply`
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct UpdateDecryptableSupplyData {
    /// The new decryptable supply
    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
    pub new_decryptable_supply: PodAeCiphertext,
}

/// Data expected by `ConfidentialMintBurnInstruction::ConfidentialMint`
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct MintInstructionData {
    /// The new decryptable supply if the mint succeeds
    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
    pub new_decryptable_supply: PodAeCiphertext,
    /// The transfer amount encrypted under the auditor ElGamal public key
    #[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
    pub mint_amount_auditor_ciphertext_lo: PodElGamalCiphertext,
    /// The transfer amount encrypted under the auditor ElGamal public key
    #[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
    pub mint_amount_auditor_ciphertext_hi: PodElGamalCiphertext,
    /// Relative location of the
    /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction
    /// to the `ConfidentialMint` instruction in the transaction. 0 if the
    /// proof is in a pre-verified context account
    pub equality_proof_instruction_offset: i8,
    /// Relative location of the
    /// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity`
    /// instruction to the `ConfidentialMint` instruction in the
    /// transaction. 0 if the proof is in a pre-verified context account
    pub ciphertext_validity_proof_instruction_offset: i8,
    /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU128`
    /// instruction to the `ConfidentialMint` instruction in the
    /// transaction. 0 if the proof is in a pre-verified context account
    pub range_proof_instruction_offset: i8,
}

/// Data expected by `ConfidentialMintBurnInstruction::ConfidentialBurn`
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct BurnInstructionData {
    /// The new decryptable balance of the burner if the burn succeeds
    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
    pub new_decryptable_available_balance: DecryptableBalance,
    /// The transfer amount encrypted under the auditor ElGamal public key
    #[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
    pub burn_amount_auditor_ciphertext_lo: PodElGamalCiphertext,
    /// The transfer amount encrypted under the auditor ElGamal public key
    #[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
    pub burn_amount_auditor_ciphertext_hi: PodElGamalCiphertext,
    /// Relative location of the
    /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction
    /// to the `ConfidentialMint` instruction in the transaction. 0 if the
    /// proof is in a pre-verified context account
    pub equality_proof_instruction_offset: i8,
    /// Relative location of the
    /// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity`
    /// instruction to the `ConfidentialMint` instruction in the
    /// transaction. 0 if the proof is in a pre-verified context account
    pub ciphertext_validity_proof_instruction_offset: i8,
    /// Relative location of the `ProofInstruction::VerifyBatchedRangeProofU128`
    /// instruction to the `ConfidentialMint` instruction in the
    /// transaction. 0 if the proof is in a pre-verified context account
    pub range_proof_instruction_offset: i8,
}

/// Create a `InitializeMint` instruction
pub fn initialize_mint(
    token_program_id: &Address,
    mint: &Address,
    supply_elgamal_pubkey: &PodElGamalPubkey,
    decryptable_supply: &DecryptableBalance,
) -> Result<Instruction, ProgramError> {
    check_program_account(token_program_id)?;
    let accounts = vec![AccountMeta::new(*mint, false)];

    Ok(encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::ConfidentialMintBurnExtension,
        ConfidentialMintBurnInstruction::InitializeMint,
        &InitializeMintData {
            supply_elgamal_pubkey: *supply_elgamal_pubkey,
            decryptable_supply: *decryptable_supply,
        },
    ))
}

/// Create a `RotateSupplyElGamalPubkey` instruction
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn rotate_supply_elgamal_pubkey(
    token_program_id: &Address,
    mint: &Address,
    authority: &Address,
    multisig_signers: &[&Address],
    new_supply_elgamal_pubkey: &PodElGamalPubkey,
    ciphertext_equality_proof: ProofLocation<CiphertextCiphertextEqualityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
    check_program_account(token_program_id)?;
    let mut accounts = vec![AccountMeta::new(*mint, false)];

    let mut expected_instruction_offset = 1;
    let mut proof_instructions = vec![];

    let proof_instruction_offset = process_proof_location(
        &mut accounts,
        &mut expected_instruction_offset,
        &mut proof_instructions,
        ciphertext_equality_proof,
        true,
        ProofInstruction::VerifyCiphertextCiphertextEquality,
    )?;

    accounts.push(AccountMeta::new_readonly(
        *authority,
        multisig_signers.is_empty(),
    ));
    for multisig_signer in multisig_signers.iter() {
        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
    }

    let mut instructions = vec![encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::ConfidentialMintBurnExtension,
        ConfidentialMintBurnInstruction::RotateSupplyElGamalPubkey,
        &RotateSupplyElGamalPubkeyData {
            new_supply_elgamal_pubkey: *new_supply_elgamal_pubkey,
            proof_instruction_offset,
        },
    )];

    instructions.extend(proof_instructions);

    Ok(instructions)
}

/// Create a `UpdateDecryptableSupply` instruction
#[cfg(not(target_os = "solana"))]
pub fn update_decryptable_supply(
    token_program_id: &Address,
    mint: &Address,
    authority: &Address,
    multisig_signers: &[&Address],
    new_decryptable_supply: &DecryptableBalance,
) -> Result<Instruction, ProgramError> {
    check_program_account(token_program_id)?;
    let mut accounts = vec![
        AccountMeta::new(*mint, false),
        AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
    ];
    for multisig_signer in multisig_signers.iter() {
        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
    }
    Ok(encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::ConfidentialMintBurnExtension,
        ConfidentialMintBurnInstruction::UpdateDecryptableSupply,
        &UpdateDecryptableSupplyData {
            new_decryptable_supply: *new_decryptable_supply,
        },
    ))
}

/// Context state accounts used in confidential mint
#[derive(Clone, Copy)]
pub struct MintSplitContextStateAccounts<'a> {
    /// Location of equality proof
    pub equality_proof: &'a Address,
    /// Location of ciphertext validity proof
    pub ciphertext_validity_proof: &'a Address,
    /// Location of range proof
    pub range_proof: &'a Address,
    /// Authority able to close proof accounts
    pub authority: &'a Address,
}

/// Create an inner `ConfidentialMint` instruction
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn inner_confidential_mint(
    token_program_id: &Address,
    token_account: &Address,
    mint: &Address,
    mint_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
    mint_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
    authority: &Address,
    multisig_signers: &[&Address],
    equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
    ciphertext_validity_proof_location: ProofLocation<
        BatchedGroupedCiphertext3HandlesValidityProofData,
    >,
    range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
    new_decryptable_supply: &DecryptableBalance,
) -> Result<Instruction, ProgramError> {
    check_program_account(token_program_id)?;
    let mut accounts = vec![
        AccountMeta::new(*token_account, false),
        AccountMeta::new(*mint, false),
    ];

    // If at least one of the proof locations is an instruction offset, the sysvar
    // account is needed.
    if equality_proof_location.is_instruction_offset()
        || ciphertext_validity_proof_location.is_instruction_offset()
        || range_proof_location.is_instruction_offset()
    {
        accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
    }

    let equality_proof_instruction_offset = match equality_proof_location {
        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
            proof_instruction_offset.into()
        }
        ProofLocation::ContextStateAccount(context_state_account) => {
            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
            0
        }
    };

    let ciphertext_validity_proof_instruction_offset = match ciphertext_validity_proof_location {
        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
            proof_instruction_offset.into()
        }
        ProofLocation::ContextStateAccount(context_state_account) => {
            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
            0
        }
    };

    let range_proof_instruction_offset = match range_proof_location {
        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
            proof_instruction_offset.into()
        }
        ProofLocation::ContextStateAccount(context_state_account) => {
            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
            0
        }
    };

    accounts.push(AccountMeta::new_readonly(
        *authority,
        multisig_signers.is_empty(),
    ));

    for multisig_signer in multisig_signers.iter() {
        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
    }

    Ok(encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::ConfidentialMintBurnExtension,
        ConfidentialMintBurnInstruction::Mint,
        &MintInstructionData {
            new_decryptable_supply: *new_decryptable_supply,
            mint_amount_auditor_ciphertext_lo: *mint_amount_auditor_ciphertext_lo,
            mint_amount_auditor_ciphertext_hi: *mint_amount_auditor_ciphertext_hi,
            equality_proof_instruction_offset,
            ciphertext_validity_proof_instruction_offset,
            range_proof_instruction_offset,
        },
    ))
}

/// Create a `ConfidentialMint` instruction
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn confidential_mint_with_split_proofs(
    token_program_id: &Address,
    token_account: &Address,
    mint: &Address,
    mint_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
    mint_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
    authority: &Address,
    multisig_signers: &[&Address],
    equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
    ciphertext_validity_proof_location: ProofLocation<
        BatchedGroupedCiphertext3HandlesValidityProofData,
    >,
    range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
    new_decryptable_supply: &DecryptableBalance,
) -> Result<Vec<Instruction>, ProgramError> {
    let mut instructions = vec![inner_confidential_mint(
        token_program_id,
        token_account,
        mint,
        mint_amount_auditor_ciphertext_lo,
        mint_amount_auditor_ciphertext_hi,
        authority,
        multisig_signers,
        equality_proof_location,
        ciphertext_validity_proof_location,
        range_proof_location,
        new_decryptable_supply,
    )?];

    let mut expected_instruction_offset = 1;

    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
        equality_proof_location
    {
        let proof_instruction_offset: i8 = proof_instruction_offset.into();
        if proof_instruction_offset != expected_instruction_offset {
            return Err(TokenError::InvalidProofInstructionOffset.into());
        }
        instructions.push(
            ProofInstruction::VerifyCiphertextCommitmentEquality
                .encode_verify_proof(None, proof_data),
        );
        expected_instruction_offset += 1;
    }

    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
        ciphertext_validity_proof_location
    {
        let proof_instruction_offset: i8 = proof_instruction_offset.into();
        if proof_instruction_offset != expected_instruction_offset {
            return Err(TokenError::InvalidProofInstructionOffset.into());
        }
        instructions.push(
            ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity
                .encode_verify_proof(None, proof_data),
        );
        expected_instruction_offset += 1;
    }

    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
        range_proof_location
    {
        let proof_instruction_offset: i8 = proof_instruction_offset.into();
        if proof_instruction_offset != expected_instruction_offset {
            return Err(TokenError::InvalidProofInstructionOffset.into());
        }
        instructions.push(
            ProofInstruction::VerifyBatchedRangeProofU128.encode_verify_proof(None, proof_data),
        );
    }

    Ok(instructions)
}

/// Create an inner `ConfidentialBurn` instruction
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn inner_confidential_burn(
    token_program_id: &Address,
    token_account: &Address,
    mint: &Address,
    new_decryptable_available_balance: &DecryptableBalance,
    burn_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
    burn_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
    authority: &Address,
    multisig_signers: &[&Address],
    equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
    ciphertext_validity_proof_location: ProofLocation<
        BatchedGroupedCiphertext3HandlesValidityProofData,
    >,
    range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
) -> Result<Instruction, ProgramError> {
    check_program_account(token_program_id)?;
    let mut accounts = vec![
        AccountMeta::new(*token_account, false),
        AccountMeta::new(*mint, false),
    ];

    if equality_proof_location.is_instruction_offset()
        || ciphertext_validity_proof_location.is_instruction_offset()
        || range_proof_location.is_instruction_offset()
    {
        accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
    }

    let equality_proof_instruction_offset = match equality_proof_location {
        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
            proof_instruction_offset.into()
        }
        ProofLocation::ContextStateAccount(context_state_account) => {
            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
            0
        }
    };

    let ciphertext_validity_proof_instruction_offset = match ciphertext_validity_proof_location {
        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
            proof_instruction_offset.into()
        }
        ProofLocation::ContextStateAccount(context_state_account) => {
            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
            0
        }
    };

    let range_proof_instruction_offset = match range_proof_location {
        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
            proof_instruction_offset.into()
        }
        ProofLocation::ContextStateAccount(context_state_account) => {
            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
            0
        }
    };

    accounts.push(AccountMeta::new_readonly(
        *authority,
        multisig_signers.is_empty(),
    ));

    for multisig_signer in multisig_signers.iter() {
        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
    }

    Ok(encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::ConfidentialMintBurnExtension,
        ConfidentialMintBurnInstruction::Burn,
        &BurnInstructionData {
            new_decryptable_available_balance: *new_decryptable_available_balance,
            burn_amount_auditor_ciphertext_lo: *burn_amount_auditor_ciphertext_lo,
            burn_amount_auditor_ciphertext_hi: *burn_amount_auditor_ciphertext_hi,
            equality_proof_instruction_offset,
            ciphertext_validity_proof_instruction_offset,
            range_proof_instruction_offset,
        },
    ))
}

/// Create a `ConfidentialBurn` instruction
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "solana"))]
pub fn confidential_burn_with_split_proofs(
    token_program_id: &Address,
    token_account: &Address,
    mint: &Address,
    new_decryptable_available_balance: &DecryptableBalance,
    burn_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
    burn_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
    authority: &Address,
    multisig_signers: &[&Address],
    equality_proof_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
    ciphertext_validity_proof_location: ProofLocation<
        BatchedGroupedCiphertext3HandlesValidityProofData,
    >,
    range_proof_location: ProofLocation<BatchedRangeProofU128Data>,
) -> Result<Vec<Instruction>, ProgramError> {
    let mut instructions = vec![inner_confidential_burn(
        token_program_id,
        token_account,
        mint,
        new_decryptable_available_balance,
        burn_amount_auditor_ciphertext_lo,
        burn_amount_auditor_ciphertext_hi,
        authority,
        multisig_signers,
        equality_proof_location,
        ciphertext_validity_proof_location,
        range_proof_location,
    )?];

    let mut expected_instruction_offset = 1;

    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
        equality_proof_location
    {
        let proof_instruction_offset: i8 = proof_instruction_offset.into();
        if proof_instruction_offset != expected_instruction_offset {
            return Err(TokenError::InvalidProofInstructionOffset.into());
        }
        instructions.push(
            ProofInstruction::VerifyCiphertextCommitmentEquality
                .encode_verify_proof(None, proof_data),
        );
        expected_instruction_offset += 1;
    }

    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
        ciphertext_validity_proof_location
    {
        let proof_instruction_offset: i8 = proof_instruction_offset.into();
        if proof_instruction_offset != expected_instruction_offset {
            return Err(TokenError::InvalidProofInstructionOffset.into());
        }
        instructions.push(
            ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity
                .encode_verify_proof(None, proof_data),
        );
        expected_instruction_offset += 1;
    }

    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
        range_proof_location
    {
        let proof_instruction_offset: i8 = proof_instruction_offset.into();
        if proof_instruction_offset != expected_instruction_offset {
            return Err(TokenError::InvalidProofInstructionOffset.into());
        }
        instructions.push(
            ProofInstruction::VerifyBatchedRangeProofU128.encode_verify_proof(None, proof_data),
        );
    }

    Ok(instructions)
}

/// Create a `ApplyPendingBurn` instruction
pub fn apply_pending_burn(
    token_program_id: &Address,
    mint: &Address,
    authority: &Address,
    multisig_signers: &[&Address],
) -> Result<Instruction, ProgramError> {
    check_program_account(token_program_id)?;
    let mut accounts = vec![
        AccountMeta::new(*mint, false),
        AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
    ];
    for multisig_signer in multisig_signers.iter() {
        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
    }
    Ok(encode_instruction(
        token_program_id,
        accounts,
        TokenInstruction::ConfidentialMintBurnExtension,
        ConfidentialMintBurnInstruction::ApplyPendingBurn,
        &(),
    ))
}
