import type { Address } from '@solana/addresses';
import type {
    Base58EncodedBytes,
    Base64EncodedDataResponse,
    Blockhash,
    Lamports,
    Reward,
    TokenBalance,
    TransactionConfig,
    TransactionError,
    TransactionStatus,
} from '@solana/rpc-types';

/**
 * The data returned by the most-recently executed instruction in a transaction, if any.
 */
export type ReturnData = {
    /** A tuple whose first element is the bytes of the return data as a base64-encoded string. */
    data: Base64EncodedDataResponse;
    /** The address of the program that generated the return data */
    programId: Address;
};

/**
 * The status metadata recorded for a transaction, independent of how the transaction itself is
 * encoded.
 *
 * This is the common base shared by every encoding of a confirmed transaction. Encoding-specific
 * additions (parsed vs. non-parsed inner instructions, loaded addresses) are layered on top of this
 * type.
 */
export type TransactionMetaBase = Readonly<{
    /** Number of compute units consumed by the transaction */
    computeUnitsConsumed?: bigint;
    /**
     * Total cost-model units charged, when reported by the validator.
     */
    costUnits?: bigint;
    /** Error if transaction failed, `null` if transaction succeeded. */
    err: TransactionError | null;
    /** The fee this transaction was charged, in {@link Lamports} */
    fee: Lamports;
    /**
     * String log messages or `null` if log message recording was not enabled during this
     * transaction
     */
    logMessages: readonly string[] | null;
    /** Account balances after the transaction was processed */
    postBalances: readonly Lamports[];
    /**
     * List of token balances from after the transaction was processed or omitted if token balance
     * recording was not yet enabled during this transaction
     */
    postTokenBalances?: readonly TokenBalance[];
    /** Account balances from before the transaction was processed */
    preBalances: readonly Lamports[];
    /**
     * List of token balances from before the transaction was processed or omitted if token balance
     * recording was not yet enabled during this transaction
     */
    preTokenBalances?: readonly TokenBalance[];
    /** The most-recent return data generated by an instruction in the transaction */
    returnData?: ReturnData;
    /**
     * Transaction-level rewards; currently only `"Rent"`, but other types may be added in the
     * future
     */
    rewards: readonly Reward[] | null;
    /** @deprecated */
    status: TransactionStatus;
}>;

/**
 * Describes a set of accounts loaded by a transaction from a single address lookup table.
 */
export type AddressTableLookup = Readonly<{
    /** The address of the address lookup table account. */
    accountKey: Address;
    /** Indexes of accounts in a lookup table to load as read-only. */
    readonlyIndexes: readonly number[];
    /** Indexes of accounts in a lookup table to load as writable. */
    writableIndexes: readonly number[];
}>;

/**
 * The fields common to a transaction regardless of how its message is encoded.
 */
export type TransactionBase = Readonly<{
    message: {
        /**
         * For transactions whose lifetime is specified by a recent blockhash, this is that
         * blockhash, and for transactions whose lifetime is specified by a durable nonce, this is
         * the nonce value.
         */
        recentBlockhash: Blockhash;
        /**
         * The compute budget declared by a version 1 transaction message.
         *
         * Omitted for legacy and version 0 transaction messages, which express their compute budget
         * through `ComputeBudget` program instructions instead.
         */
        transactionConfig?: TransactionConfig;
    };
    /**
     * An ordered list of signatures belonging to the accounts required to sign this transaction.
     *
     * Each signature is an Ed25519 signature of the transaction message using the private key
     * associated with the account required to sign the transaction.
     */
    signatures: readonly Base58EncodedBytes[];
}>;

/**
 * The call-stack height annotation present on instructions.
 */
export type InstructionWithStackHeight = Readonly<{
    /**
     * A number indicating the height at which this instruction was called with respect to the
     * bottom of the call stack denoted by `1` or `null`.
     *
     * For instance, an instruction explicitly declared in the transaction message will have a `1`
     * or `null` height, the first instruction that it calls using a cross-program invocation (CPI)
     * will have a height of 2, an instruction called by that instruction using a CPI will have a
     * depth of 3, and so on.
     */
    stackHeight: number; // FIXME(https://github.com/anza-xyz/agave/issues/5732) Should be `1` instead of `null` at base of stack
}>;

/**
 * The input data supplied to an invoked program.
 */
export type InstructionWithData = Readonly<{
    /** The input to the invoked program */
    data: Base58EncodedBytes;
}>;

/**
 * An instruction as it appears in a non-parsed (`json`) transaction message, referencing accounts
 * and the program by their index into the message's account list.
 */
export type TransactionInstruction = InstructionWithData &
    Partial<InstructionWithStackHeight> &
    Readonly<{
        /**
         * An ordered list of indices that indicate which accounts in the transaction message's
         * accounts list are loaded by this instruction.
         */
        accounts: readonly number[];
        /**
         * The index of the address in the transaction message's accounts list associated with the
         * program to invoke.
         */
        programIdIndex: number;
    }>;

/**
 * A confirmed transaction whose message is materialized as structured JSON (the `json` encoding).
 */
export type TransactionJson = Readonly<{
    message: {
        /** An ordered list of addresses belonging to the accounts loaded by this transaction */
        accountKeys: readonly Address[];
        header: {
            /**
             * The number of read-only accounts in the static accounts list that must sign this
             * transaction.
             *
             * Subtracting this number from `numRequiredSignatures` yields the index of the first
             * read-only signer account in the static accounts list.
             */
            numReadonlySignedAccounts: number;
            /**
             * The number of accounts in the static accounts list that are neither writable nor
             * signers.
             *
             * Adding this number to `numRequiredSignatures` yields the index of the first read-only
             * non-signer account in the static accounts list.
             */
            numReadonlyUnsignedAccounts: number;
            /**
             * The number of accounts in the static accounts list that must sign this transaction.
             *
             * Subtracting `numReadonlySignedAccounts` from this number yields the number of
             * writable signer accounts in the static accounts list. Writable signer accounts always
             * begin at index zero in the static accounts list.
             *
             * This number itself is the index of the first non-signer account in the static
             * accounts list.
             */
            numRequiredSignatures: number;
        };
        instructions: readonly TransactionInstruction[];
    };
}> &
    TransactionBase;

/**
 * An instruction in a `jsonParsed` transaction whose owning program has no instruction parser, so
 * it materializes as a list of accounts, a program address, and base58-encoded instruction data.
 */
export type PartiallyDecodedTransactionInstruction = InstructionWithData &
    Partial<InstructionWithStackHeight> &
    Readonly<{
        /** An ordered list of addresses belonging to the accounts loaded by this instruction */
        accounts: readonly Address[];
        /** The address of the program to invoke */
        programId: Address;
    }>;

/**
 * An instruction in a `jsonParsed` transaction whose owning program's instruction parser succeeded,
 * so it materializes as structured data.
 */
export type ParsedTransactionInstruction = Partial<InstructionWithStackHeight> &
    Readonly<{
        /** The output of the program's instruction parser */
        parsed: {
            /** The instruction, as interpreted the program's instruction parser. */
            info?: object;
            /**
             * A label that indicates the type of the instruction, as determined by the program's
             * instruction parser.
             */
            type: string;
        };
        /** The name of the program. */
        program: string;
        /** The address of the program */
        programId: Address;
    }>;

/**
 * An account as it appears in a `jsonParsed` transaction message.
 */
export type ParsedAccount = Readonly<{
    /** The address of the account */
    pubkey: Address;
    /** Whether this account is required to sign the transaction that it's a part of */
    signer: boolean;
    /**
     * Indicates whether the account was statically declared in the transaction message or loaded
     * from an address lookup table.
     */
    source: 'lookupTable' | 'transaction';
    /** Whether this account must be loaded with a write-lock */
    writable: boolean;
}>;

/**
 * A confirmed transaction whose message has been processed by account and instruction parsers (the
 * `jsonParsed` encoding).
 */
export type TransactionJsonParsed = Readonly<{
    message: {
        /**
         * An ordered list of parsed accounts belonging to the accounts loaded by this transaction
         */
        accountKeys: readonly ParsedAccount[];
        instructions: readonly (ParsedTransactionInstruction | PartiallyDecodedTransactionInstruction)[];
    };
}> &
    TransactionBase;

/**
 * The addresses a versioned transaction loaded from address lookup tables, as recorded in its
 * metadata.
 */
export type TransactionMetaLoadedAddresses = Readonly<{
    /** Addresses loaded from lookup tables */
    loadedAddresses: {
        /** Ordered list of base-58 encoded addresses for read-only accounts */
        readonly: readonly Address[];
        /** Ordered list of base-58 encoded addresses for writable accounts */
        writable: readonly Address[];
    };
}>;

/**
 * A set of instructions called by a single top-level instruction via cross-program invocation.
 *
 * @typeParam TInstructionType - The shape of each inner instruction, which varies by encoding.
 */
export type InnerInstructions<TInstructionType> = Readonly<{
    /** The index of the instruction in the transaction */
    index: number;
    /** The instructions */
    instructions: readonly TInstructionType[];
}>;

/**
 * The inner-instruction metadata of a non-parsed (`json`/`base58`/`base64`) transaction.
 */
export type TransactionMetaInnerInstructionsNotParsed = Readonly<{
    /** A list of instructions called by programs via cross-program invocation (CPI) */
    innerInstructions?: readonly InnerInstructions<TransactionInstruction>[] | null;
}>;

/**
 * The inner-instruction metadata of a `jsonParsed` transaction.
 */
export type TransactionMetaInnerInstructionsParsed = Readonly<{
    /** A list of instructions called by programs via cross-program invocation (CPI) */
    innerInstructions?:
        | readonly InnerInstructions<ParsedTransactionInstruction | PartiallyDecodedTransactionInstruction>[]
        | null;
}>;

/**
 * The address-lookup-table references carried by a versioned transaction's message.
 */
export type TransactionAddressTableLookups = Readonly<{
    message: Readonly<{
        /** A list of address tables and the accounts that this transaction loads from them */
        addressTableLookups: readonly AddressTableLookup[];
    }>;
}>;
