import { getAddressCodec, getNullableCodec, getNullableDecoder, getNullableEncoder, getStructDecoder, getStructEncoder, getU8Codec, } from '@solana/kit';
import { ByteCursor, concatBytes, option, u256le, u32le, u8, vec, } from '../codecs/binary.js';
import { decodeInterchainGasPaymasterType, encodeGasRouterConfig, encodeH256, encodeInterchainGasPaymasterType, encodeRemoteRouterConfig, InterchainGasPaymasterTypeKind, } from '../codecs/shared.js';
import { PROGRAM_INSTRUCTION_DISCRIMINATOR, SYSTEM_PROGRAM_ADDRESS, } from '../constants.js';
import { deriveHyperlaneTokenPda, deriveMailboxDispatchAuthorityPda, } from '../pda.js';
import { buildInstruction, readonlyAccount, readonlySignerAddress, writableAccount, writableSigner, writableSignerAddress, } from './utils.js';
export var TokenProgramInstructionKind;
(function (TokenProgramInstructionKind) {
    TokenProgramInstructionKind[TokenProgramInstructionKind["Init"] = 0] = "Init";
    TokenProgramInstructionKind[TokenProgramInstructionKind["TransferRemote"] = 1] = "TransferRemote";
    TokenProgramInstructionKind[TokenProgramInstructionKind["EnrollRemoteRouter"] = 2] = "EnrollRemoteRouter";
    TokenProgramInstructionKind[TokenProgramInstructionKind["EnrollRemoteRouters"] = 3] = "EnrollRemoteRouters";
    TokenProgramInstructionKind[TokenProgramInstructionKind["SetDestinationGasConfigs"] = 4] = "SetDestinationGasConfigs";
    TokenProgramInstructionKind[TokenProgramInstructionKind["SetInterchainSecurityModule"] = 5] = "SetInterchainSecurityModule";
    TokenProgramInstructionKind[TokenProgramInstructionKind["SetInterchainGasPaymaster"] = 6] = "SetInterchainGasPaymaster";
    TokenProgramInstructionKind[TokenProgramInstructionKind["TransferOwnership"] = 7] = "TransferOwnership";
})(TokenProgramInstructionKind || (TokenProgramInstructionKind = {}));
const ADDRESS_CODEC = getAddressCodec();
const OPTIONAL_ADDRESS_CODEC = getNullableCodec(ADDRESS_CODEC);
const U8_CODEC = getU8Codec();
const IGP_VALUE_CODEC = getStructEncoder([
    ['programId', ADDRESS_CODEC],
    ['igpKind', U8_CODEC],
    ['igpAccount', ADDRESS_CODEC],
]);
const IGP_VALUE_DECODER = getStructDecoder([
    ['programId', ADDRESS_CODEC],
    ['igpKind', U8_CODEC],
    ['igpAccount', ADDRESS_CODEC],
]);
const OPTIONAL_IGP_ENCODER = getNullableEncoder(IGP_VALUE_CODEC);
const OPTIONAL_IGP_DECODER = getNullableDecoder(IGP_VALUE_DECODER);
const TOKEN_INIT_ENCODER = getStructEncoder([
    ['mailbox', ADDRESS_CODEC],
    ['interchainSecurityModule', OPTIONAL_ADDRESS_CODEC],
    ['interchainGasPaymaster', OPTIONAL_IGP_ENCODER],
    ['decimals', U8_CODEC],
    ['remoteDecimals', U8_CODEC],
]);
const TOKEN_INIT_DECODER = getStructDecoder([
    ['mailbox', ADDRESS_CODEC],
    ['interchainSecurityModule', OPTIONAL_ADDRESS_CODEC],
    ['interchainGasPaymaster', OPTIONAL_IGP_DECODER],
    ['decimals', U8_CODEC],
    ['remoteDecimals', U8_CODEC],
]);
export function encodeTokenProgramInstruction(instruction) {
    switch (instruction.kind) {
        case 'init':
            return concatBytes(PROGRAM_INSTRUCTION_DISCRIMINATOR, u8(TokenProgramInstructionKind.Init), encodeTokenInit(instruction.value));
        case 'transferRemote':
            return concatBytes(PROGRAM_INSTRUCTION_DISCRIMINATOR, u8(TokenProgramInstructionKind.TransferRemote), encodeTransferRemote(instruction.value));
        case 'enrollRemoteRouter':
            return concatBytes(PROGRAM_INSTRUCTION_DISCRIMINATOR, u8(TokenProgramInstructionKind.EnrollRemoteRouter), encodeRemoteRouterConfig(instruction.value));
        case 'enrollRemoteRouters':
            return concatBytes(PROGRAM_INSTRUCTION_DISCRIMINATOR, u8(TokenProgramInstructionKind.EnrollRemoteRouters), vec(instruction.value, encodeRemoteRouterConfig));
        case 'setDestinationGasConfigs':
            return concatBytes(PROGRAM_INSTRUCTION_DISCRIMINATOR, u8(TokenProgramInstructionKind.SetDestinationGasConfigs), vec(instruction.value, encodeGasRouterConfig));
        case 'setInterchainSecurityModule':
            return concatBytes(PROGRAM_INSTRUCTION_DISCRIMINATOR, u8(TokenProgramInstructionKind.SetInterchainSecurityModule), option(instruction.value, (addr) => ADDRESS_CODEC.encode(addr)));
        case 'setInterchainGasPaymaster':
            return concatBytes(PROGRAM_INSTRUCTION_DISCRIMINATOR, u8(TokenProgramInstructionKind.SetInterchainGasPaymaster), option(instruction.value, ([programId, igp]) => concatBytes(ADDRESS_CODEC.encode(programId), encodeInterchainGasPaymasterType(igp))));
        case 'transferOwnership':
            return concatBytes(PROGRAM_INSTRUCTION_DISCRIMINATOR, u8(TokenProgramInstructionKind.TransferOwnership), option(instruction.value, (addr) => ADDRESS_CODEC.encode(addr)));
    }
}
export function decodeTokenProgramInstruction(data) {
    if (data.length < 9)
        return null;
    const cursor = new ByteCursor(data);
    const prefix = cursor.readBytes(8);
    if (!prefix.every((v) => v === 1))
        return null;
    const kind = cursor.readU8();
    switch (kind) {
        case TokenProgramInstructionKind.Init:
            return {
                kind: 'init',
                value: decodeTokenInit(cursor.readBytes(cursor.remaining())),
            };
        case TokenProgramInstructionKind.TransferRemote:
            return { kind: 'transferRemote', value: decodeTransferRemote(cursor) };
        case TokenProgramInstructionKind.EnrollRemoteRouter:
            return {
                kind: 'enrollRemoteRouter',
                value: decodeRemoteRouterConfig(cursor),
            };
        case TokenProgramInstructionKind.EnrollRemoteRouters:
            return {
                kind: 'enrollRemoteRouters',
                value: decodeVec(cursor, decodeRemoteRouterConfig),
            };
        case TokenProgramInstructionKind.SetDestinationGasConfigs:
            return {
                kind: 'setDestinationGasConfigs',
                value: decodeVec(cursor, decodeGasRouterConfig),
            };
        case TokenProgramInstructionKind.SetInterchainSecurityModule:
            return {
                kind: 'setInterchainSecurityModule',
                value: decodeOptionAddress(cursor),
            };
        case TokenProgramInstructionKind.SetInterchainGasPaymaster:
            return {
                kind: 'setInterchainGasPaymaster',
                value: decodeOptionIgpTuple(cursor),
            };
        case TokenProgramInstructionKind.TransferOwnership:
            return { kind: 'transferOwnership', value: decodeOptionAddress(cursor) };
        default:
            if (kind <= TokenProgramInstructionKind.TransferOwnership) {
                throw new Error(`Token instruction kind ${kind} is recognized but decoding is not yet implemented`);
            }
            return null;
    }
}
export async function getTokenInitInstruction(programAddress, payer, init, pluginAccounts) {
    const { address: tokenPda } = await deriveHyperlaneTokenPda(programAddress);
    const { address: dispatchAuthority } = await deriveMailboxDispatchAuthorityPda(programAddress);
    return buildInstruction(programAddress, [
        readonlyAccount(SYSTEM_PROGRAM_ADDRESS),
        writableAccount(tokenPda),
        writableAccount(dispatchAuthority),
        writableSigner(payer),
        ...pluginAccounts,
    ], encodeTokenProgramInstruction({ kind: 'init', value: init }));
}
export async function getTokenTransferOwnershipInstruction(programAddress, owner, newOwner) {
    const { address: tokenPda } = await deriveHyperlaneTokenPda(programAddress);
    return buildInstruction(programAddress, [writableAccount(tokenPda), readonlySignerAddress(owner)], encodeTokenProgramInstruction({
        kind: 'transferOwnership',
        value: newOwner,
    }));
}
export async function getTokenSetInterchainSecurityModuleInstruction(programAddress, owner, newIsm) {
    const { address: tokenPda } = await deriveHyperlaneTokenPda(programAddress);
    return buildInstruction(programAddress, [writableAccount(tokenPda), readonlySignerAddress(owner)], encodeTokenProgramInstruction({
        kind: 'setInterchainSecurityModule',
        value: newIsm,
    }));
}
export async function getTokenSetInterchainGasPaymasterInstruction(programAddress, owner, value) {
    const { address: tokenPda } = await deriveHyperlaneTokenPda(programAddress);
    return buildInstruction(programAddress, [writableAccount(tokenPda), readonlySignerAddress(owner)], encodeTokenProgramInstruction({ kind: 'setInterchainGasPaymaster', value }));
}
export async function getTokenEnrollRemoteRoutersInstruction(programAddress, owner, routers) {
    const { address: tokenPda } = await deriveHyperlaneTokenPda(programAddress);
    return buildInstruction(programAddress, [
        readonlyAccount(SYSTEM_PROGRAM_ADDRESS),
        writableAccount(tokenPda),
        writableSignerAddress(owner),
    ], encodeTokenProgramInstruction({
        kind: 'enrollRemoteRouters',
        value: routers,
    }));
}
export async function getTokenSetDestinationGasConfigsInstruction(programAddress, owner, gasConfigs) {
    const { address: tokenPda } = await deriveHyperlaneTokenPda(programAddress);
    return buildInstruction(programAddress, [
        readonlyAccount(SYSTEM_PROGRAM_ADDRESS),
        writableAccount(tokenPda),
        writableSignerAddress(owner),
    ], encodeTokenProgramInstruction({
        kind: 'setDestinationGasConfigs',
        value: gasConfigs,
    }));
}
export function encodeTokenInit(value) {
    const normalized = {
        mailbox: value.mailbox,
        interchainSecurityModule: value.interchainSecurityModule,
        interchainGasPaymaster: value.interchainGasPaymaster
            ? {
                programId: value.interchainGasPaymaster.programId,
                igpKind: value.interchainGasPaymaster.igp.kind,
                igpAccount: value.interchainGasPaymaster.igp.account,
            }
            : null,
        decimals: value.decimals,
        remoteDecimals: value.remoteDecimals,
    };
    return Uint8Array.from(TOKEN_INIT_ENCODER.encode(normalized));
}
function validateIgpKind(value) {
    if (value !== InterchainGasPaymasterTypeKind.Igp &&
        value !== InterchainGasPaymasterTypeKind.OverheadIgp) {
        throw new Error(`Unknown InterchainGasPaymasterTypeKind: ${value}`);
    }
    return value;
}
function decodeTokenInit(data) {
    const decoded = TOKEN_INIT_DECODER.decode(data);
    const igp = decoded.interchainGasPaymaster;
    return {
        mailbox: decoded.mailbox,
        interchainSecurityModule: decoded.interchainSecurityModule,
        interchainGasPaymaster: igp
            ? {
                programId: igp.programId,
                igp: {
                    kind: validateIgpKind(igp.igpKind),
                    account: igp.igpAccount,
                },
            }
            : null,
        decimals: decoded.decimals,
        remoteDecimals: decoded.remoteDecimals,
    };
}
function encodeTransferRemote(value) {
    return concatBytes(u32le(value.destinationDomain), encodeH256(value.recipient), u256le(value.amountOrId));
}
function decodeTransferRemote(cursor) {
    return {
        destinationDomain: cursor.readU32LE(),
        recipient: cursor.readBytes(32),
        amountOrId: cursor.readU256LE(),
    };
}
function decodeRemoteRouterConfig(cursor) {
    const domain = cursor.readU32LE();
    const hasRouter = cursor.readU8() === 1;
    return {
        domain,
        router: hasRouter ? cursor.readBytes(32) : null,
    };
}
function decodeGasRouterConfig(cursor) {
    const domain = cursor.readU32LE();
    const hasGas = cursor.readU8() === 1;
    return {
        domain,
        gas: hasGas ? cursor.readU64LE() : null,
    };
}
function decodeOptionAddress(cursor) {
    const hasValue = cursor.readU8() === 1;
    return hasValue ? ADDRESS_CODEC.decode(cursor.readBytes(32)) : null;
}
function decodeOptionIgpTuple(cursor) {
    const hasValue = cursor.readU8() === 1;
    if (!hasValue)
        return null;
    return [
        ADDRESS_CODEC.decode(cursor.readBytes(32)),
        decodeInterchainGasPaymasterType(cursor),
    ];
}
function decodeVec(cursor, decoder) {
    const length = cursor.readU32LE();
    const out = [];
    for (let i = 0; i < length; i += 1) {
        out.push(decoder(cursor));
    }
    return out;
}
//# sourceMappingURL=token.js.map