import { address as parseAddress } from '@solana/kit';
import { ArtifactState, } from '@hyperlane-xyz/provider-sdk/artifact';
import { ZERO_ADDRESS_HEX_32, assert, eqAddressSol, eqOptionalAddress, isEmptyAddress, isZeroishAddress, } from '@hyperlane-xyz/utils';
import { resolveProgram } from '../deploy/resolve-program.js';
import { buildInitMailboxInstruction, buildSetDefaultIsmInstruction, buildTransferMailboxOwnershipInstruction, } from './mailbox-tx.js';
import { fetchMailboxInboxAccount, fetchMailboxOutboxAccount, } from './mailbox-query.js';
import { DEFAULT_COMPUTE_UNITS } from '../constants.js';
// Default protocol fee values for mailbox initialization.
const DEFAULT_MAX_PROTOCOL_FEE = 1000000000n;
const DEFAULT_PROTOCOL_FEE = 0n;
export class SvmMailboxReader {
    rpc;
    constructor(rpc) {
        this.rpc = rpc;
    }
    async read(address) {
        const programId = parseAddress(address);
        const inbox = await fetchMailboxInboxAccount(this.rpc, programId);
        assert(inbox, `Mailbox inbox not initialized at ${programId}`);
        const outbox = await fetchMailboxOutboxAccount(this.rpc, programId);
        assert(outbox, `Mailbox outbox not initialized at ${programId}`);
        // On SVM the mailbox IS the merkle tree hook — return the mailbox
        // address for both defaultHook and requiredHook as UNDERIVED artifacts.
        // NOTE: This is lossy. The SVM outbox account has no hook fields, so we
        // cannot recover the original hook config (e.g. IGP). A `core read` →
        // `core apply` round-trip with a non-merkleTreeHook config will redeploy
        // hooks that already exist.
        // Original hook addresses can be retrieved from the registry addresses if needed.
        const mailboxHookRef = {
            artifactState: ArtifactState.UNDERIVED,
            deployed: { address: programId },
        };
        const config = {
            owner: outbox.owner ?? ZERO_ADDRESS_HEX_32,
            defaultIsm: {
                artifactState: ArtifactState.UNDERIVED,
                deployed: { address: inbox.defaultIsm },
            },
            defaultHook: mailboxHookRef,
            requiredHook: mailboxHookRef,
        };
        return {
            artifactState: ArtifactState.DEPLOYED,
            config,
            deployed: {
                address: programId,
                domainId: inbox.localDomain,
            },
        };
    }
}
export class SvmMailboxWriter extends SvmMailboxReader {
    config;
    svmSigner;
    constructor(config, rpc, svmSigner) {
        super(rpc);
        this.config = config;
        this.svmSigner = svmSigner;
    }
    async create(artifact) {
        const receipts = [];
        const mailboxConfig = artifact.config;
        const { programAddress, receipts: deployReceipts } = await resolveProgram(this.config.program, this.svmSigner, this.rpc);
        receipts.push(...deployReceipts);
        const existing = await fetchMailboxInboxAccount(this.rpc, programAddress);
        if (existing) {
            return [await this.read(programAddress), receipts];
        }
        const initData = {
            localDomain: this.config.domainId,
            defaultIsm: parseAddress(mailboxConfig.defaultIsm.deployed.address),
            maxProtocolFee: DEFAULT_MAX_PROTOCOL_FEE,
            protocolFee: {
                fee: DEFAULT_PROTOCOL_FEE,
                beneficiary: this.svmSigner.signer.address,
            },
        };
        const initIx = await buildInitMailboxInstruction(programAddress, this.svmSigner.signer, initData);
        receipts.push(await this.svmSigner.send({
            instructions: [initIx],
            computeUnits: DEFAULT_COMPUTE_UNITS,
            skipPreflight: true,
        }));
        // Ownership is always set to signer at init time.
        // Ownership transfer is handled separately via update().
        return [
            {
                artifactState: ArtifactState.DEPLOYED,
                config: { ...mailboxConfig, owner: this.svmSigner.signer.address },
                deployed: {
                    address: programAddress,
                    domainId: this.config.domainId,
                },
            },
            receipts,
        ];
    }
    async update(artifact) {
        const programId = parseAddress(artifact.deployed.address);
        const current = await this.read(programId);
        assert(!isZeroishAddress(current.config.owner), `Cannot update mailbox ${programId}: mailbox has no owner`);
        const expected = artifact.config;
        const ownerAddress = parseAddress(current.config.owner);
        const txs = [];
        // 1. Default ISM update
        const currentIsm = current.config.defaultIsm.deployed.address;
        const expectedIsm = expected.defaultIsm.deployed.address;
        if (!eqAddressSol(currentIsm, expectedIsm)) {
            txs.push({
                feePayer: ownerAddress,
                instructions: [
                    await buildSetDefaultIsmInstruction(programId, ownerAddress, parseAddress(expectedIsm)),
                ],
                annotation: `Update mailbox ${programId}: set default ISM`,
            });
        }
        // 2. Ownership transfer — always last
        if (!eqOptionalAddress(current.config.owner, expected.owner, eqAddressSol)) {
            const expectedOwner = !isEmptyAddress(expected.owner)
                ? parseAddress(expected.owner)
                : null;
            txs.push({
                feePayer: ownerAddress,
                instructions: [
                    await buildTransferMailboxOwnershipInstruction(programId, ownerAddress, expectedOwner),
                ],
                annotation: `Update mailbox ${programId}: transfer ownership`,
            });
        }
        return txs;
    }
}
//# sourceMappingURL=mailbox.js.map