import { fixCodecSize, getAddressCodec, getBytesCodec, getNullableCodec, getStructDecoder, getStructEncoder, getU32Codec, getU64Codec, } from '@solana/kit';
import { concatBytes, option, u8, vec } from '../codecs/binary.js';
import { encodeGasOracleConfig, encodeGasOverheadConfig, } from '../codecs/shared.js';
import { SYSTEM_PROGRAM_ADDRESS } from '../constants.js';
import { deriveIgpAccountPda, deriveIgpProgramDataPda, deriveOverheadIgpAccountPda, } from '../pda.js';
import { buildInstruction, readonlyAccount, writableAccount, writableSigner, writableSignerAddress, } from './utils.js';
export var IgpInstructionKind;
(function (IgpInstructionKind) {
    IgpInstructionKind[IgpInstructionKind["Init"] = 0] = "Init";
    IgpInstructionKind[IgpInstructionKind["InitIgp"] = 1] = "InitIgp";
    IgpInstructionKind[IgpInstructionKind["InitOverheadIgp"] = 2] = "InitOverheadIgp";
    IgpInstructionKind[IgpInstructionKind["PayForGas"] = 3] = "PayForGas";
    IgpInstructionKind[IgpInstructionKind["QuoteGasPayment"] = 4] = "QuoteGasPayment";
    IgpInstructionKind[IgpInstructionKind["TransferIgpOwnership"] = 5] = "TransferIgpOwnership";
    IgpInstructionKind[IgpInstructionKind["TransferOverheadIgpOwnership"] = 6] = "TransferOverheadIgpOwnership";
    IgpInstructionKind[IgpInstructionKind["SetIgpBeneficiary"] = 7] = "SetIgpBeneficiary";
    IgpInstructionKind[IgpInstructionKind["SetDestinationGasOverheads"] = 8] = "SetDestinationGasOverheads";
    IgpInstructionKind[IgpInstructionKind["SetGasOracleConfigs"] = 9] = "SetGasOracleConfigs";
    IgpInstructionKind[IgpInstructionKind["Claim"] = 10] = "Claim";
})(IgpInstructionKind || (IgpInstructionKind = {}));
const BYTES32_CODEC = fixCodecSize(getBytesCodec(), 32);
const ADDRESS_CODEC = getAddressCodec();
const OPTIONAL_ADDRESS_CODEC = getNullableCodec(ADDRESS_CODEC);
const INIT_IGP_ENCODER = getStructEncoder([
    ['salt', BYTES32_CODEC],
    ['owner', OPTIONAL_ADDRESS_CODEC],
    ['beneficiary', ADDRESS_CODEC],
]);
const INIT_IGP_DECODER = getStructDecoder([
    ['salt', BYTES32_CODEC],
    ['owner', OPTIONAL_ADDRESS_CODEC],
    ['beneficiary', ADDRESS_CODEC],
]);
const INIT_OVERHEAD_IGP_ENCODER = getStructEncoder([
    ['salt', BYTES32_CODEC],
    ['owner', OPTIONAL_ADDRESS_CODEC],
    ['inner', ADDRESS_CODEC],
]);
const INIT_OVERHEAD_IGP_DECODER = getStructDecoder([
    ['salt', BYTES32_CODEC],
    ['owner', OPTIONAL_ADDRESS_CODEC],
    ['inner', ADDRESS_CODEC],
]);
const QUOTE_GAS_PAYMENT_ENCODER = getStructEncoder([
    ['destinationDomain', getU32Codec()],
    ['gasAmount', getU64Codec()],
]);
const QUOTE_GAS_PAYMENT_DECODER = getStructDecoder([
    ['destinationDomain', getU32Codec()],
    ['gasAmount', getU64Codec()],
]);
export function encodeIgpProgramInstruction(instruction) {
    switch (instruction.kind) {
        case 'init':
            return u8(IgpInstructionKind.Init);
        case 'initIgp':
            return concatBytes(u8(IgpInstructionKind.InitIgp), encodeInitIgp(instruction.value));
        case 'initOverheadIgp':
            return concatBytes(u8(IgpInstructionKind.InitOverheadIgp), encodeInitOverheadIgp(instruction.value));
        case 'quoteGasPayment':
            return concatBytes(u8(IgpInstructionKind.QuoteGasPayment), Uint8Array.from(QUOTE_GAS_PAYMENT_ENCODER.encode(instruction.value)));
        case 'transferIgpOwnership':
            return concatBytes(u8(IgpInstructionKind.TransferIgpOwnership), option(instruction.newOwner, (addr) => ADDRESS_CODEC.encode(addr)));
        case 'transferOverheadIgpOwnership':
            return concatBytes(u8(IgpInstructionKind.TransferOverheadIgpOwnership), option(instruction.newOwner, (addr) => ADDRESS_CODEC.encode(addr)));
        case 'setIgpBeneficiary':
            return concatBytes(u8(IgpInstructionKind.SetIgpBeneficiary), ADDRESS_CODEC.encode(instruction.beneficiary));
        case 'setDestinationGasOverheads':
            return concatBytes(u8(IgpInstructionKind.SetDestinationGasOverheads), vec(instruction.configs, encodeGasOverheadConfig));
        case 'setGasOracleConfigs':
            return concatBytes(u8(IgpInstructionKind.SetGasOracleConfigs), vec(instruction.configs, encodeGasOracleConfig));
        case 'claim':
            return u8(IgpInstructionKind.Claim);
    }
}
export function decodeIgpProgramInstruction(data) {
    if (data.length < 1)
        return null;
    const kind = data[0];
    const payload = data.slice(1);
    switch (kind) {
        case IgpInstructionKind.Init:
            return { kind: 'init' };
        case IgpInstructionKind.InitIgp:
            return { kind: 'initIgp', value: decodeInitIgp(payload) };
        case IgpInstructionKind.InitOverheadIgp:
            return { kind: 'initOverheadIgp', value: decodeInitOverheadIgp(payload) };
        case IgpInstructionKind.QuoteGasPayment: {
            const decoded = QUOTE_GAS_PAYMENT_DECODER.decode(payload);
            return {
                kind: 'quoteGasPayment',
                value: {
                    destinationDomain: decoded.destinationDomain,
                    gasAmount: decoded.gasAmount,
                },
            };
        }
        default:
            if (kind <= IgpInstructionKind.Claim) {
                throw new Error(`IGP instruction kind ${kind} is recognized but decoding is not yet implemented`);
            }
            return null;
    }
}
export async function getInitIgpProgramInstruction(programAddress, payer) {
    const { address: programData } = await deriveIgpProgramDataPda(programAddress);
    return buildInstruction(programAddress, [
        readonlyAccount(SYSTEM_PROGRAM_ADDRESS),
        writableSigner(payer),
        writableAccount(programData),
    ], encodeIgpProgramInstruction({ kind: 'init' }));
}
export async function getInitIgpInstruction(programAddress, payer, value) {
    const { address: igpAccount } = await deriveIgpAccountPda(programAddress, value.salt);
    return buildInstruction(programAddress, [
        readonlyAccount(SYSTEM_PROGRAM_ADDRESS),
        writableSigner(payer),
        writableAccount(igpAccount),
    ], encodeIgpProgramInstruction({ kind: 'initIgp', value }));
}
export async function getInitOverheadIgpInstruction(programAddress, payer, value) {
    const { address: overheadIgpAccount } = await deriveOverheadIgpAccountPda(programAddress, value.salt);
    return buildInstruction(programAddress, [
        readonlyAccount(SYSTEM_PROGRAM_ADDRESS),
        writableSigner(payer),
        writableAccount(overheadIgpAccount),
    ], encodeIgpProgramInstruction({ kind: 'initOverheadIgp', value }));
}
function encodeInitIgp(value) {
    return Uint8Array.from(INIT_IGP_ENCODER.encode(value));
}
function decodeInitIgp(data) {
    const decoded = INIT_IGP_DECODER.decode(data);
    return {
        salt: Uint8Array.from(decoded.salt),
        owner: decoded.owner,
        beneficiary: decoded.beneficiary,
    };
}
function encodeInitOverheadIgp(value) {
    return Uint8Array.from(INIT_OVERHEAD_IGP_ENCODER.encode(value));
}
function decodeInitOverheadIgp(data) {
    const decoded = INIT_OVERHEAD_IGP_DECODER.decode(data);
    return {
        salt: Uint8Array.from(decoded.salt),
        owner: decoded.owner,
        inner: decoded.inner,
    };
}
export async function getSetGasOracleConfigsInstruction(programAddress, owner, igpAccount, configs) {
    return buildInstruction(programAddress, [
        readonlyAccount(SYSTEM_PROGRAM_ADDRESS),
        writableAccount(igpAccount),
        writableSignerAddress(owner),
    ], encodeIgpProgramInstruction({ kind: 'setGasOracleConfigs', configs }));
}
export async function getSetDestinationGasOverheadsInstruction(programAddress, owner, overheadIgpAccount, configs) {
    return buildInstruction(programAddress, [
        readonlyAccount(SYSTEM_PROGRAM_ADDRESS),
        writableAccount(overheadIgpAccount),
        writableSignerAddress(owner),
    ], encodeIgpProgramInstruction({
        kind: 'setDestinationGasOverheads',
        configs,
    }));
}
//# sourceMappingURL=igp.js.map