import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { l1StandardBridgeAbi } from '../constants/l1StandardBridgeAbi.js';
import { multicall3Abi } from '../constants/multicall3Abi.js';
import { ChooseExecutionOption, } from '../components/ChooseExecutionOption.js';
import { useMappingChainByIdentifier } from '../queries/chainByIdentifier.js';
import { useTransactionTaskStore } from '../stores/transactionTaskStore.js';
import { zodSupportedNetwork } from '../util/fetchSuperchainRegistryChainList.js';
import { createTransactionTaskId } from '../util/transactionTask.js';
import { getBlockExplorerTxHashLink } from '../util/blockExplorer.js';
import { zodAddress, zodPrivateKey, zodValueAmount } from '../util/schemas.js';
import { chainById } from '../util/chains/chains.js';
import { Badge, Spinner } from '@inkjs/ui';
import { Box, Text } from 'ink';
import { option } from 'pastel';
import { useEffect, useState } from 'react';
import { encodeFunctionData, formatEther, formatGwei, toHex, zeroAddress, } from 'viem';
import { privateKeyToAccount, privateKeyToAddress } from 'viem/accounts';
import { useEstimateGas, useGasPrice, useWaitForTransactionReceipt, useWriteContract, } from 'wagmi';
import { z } from 'zod';
import { supersimL1 } from '@eth-optimism/viem/chains';
const zodBridgeParams = z.object({
    network: zodSupportedNetwork.describe(option({
        description: 'Network to bridge to',
        alias: 'n',
    })),
    chains: z
        .array(z.string())
        .describe(option({
        description: 'Chains to bridge to',
        alias: 'c',
    }))
        .min(1),
    amount: zodValueAmount.describe(option({
        description: 'Amount to bridge per chain',
        alias: 'a',
    })),
    privateKey: zodPrivateKey.optional().describe(option({
        description: 'Signer private key',
        alias: 'pk',
    })),
    recipient: zodAddress.optional().describe(option({
        description: 'Recipient address to bridge to',
        alias: 'r',
    })),
});
export const BridgeEntrypoint = ({ options, }) => {
    if (!options.privateKey && !options.recipient) {
        console.error('Either --private-key OR --recipient is required. Please provide one.');
        return (_jsx(Text, { color: "red", children: "Error: Either --private-key OR --recipient is required. Please provide one." }));
    }
    const { data: chainByIdentifier, isLoading: isChainByIdentifierLoading } = useMappingChainByIdentifier();
    if (isChainByIdentifierLoading || !chainByIdentifier) {
        return _jsx(Spinner, {});
    }
    const chains = options.chains.map(chain => chainByIdentifier[`${options.network}/${chain}`]);
    const sourceChain = chainById[chains[0].sourceId];
    // Defaults to the private key address if no recipient is provided
    const recipientAddress = options.recipient ?? privateKeyToAddress(options.privateKey);
    return (_jsx(BridgeInner, { sourceChain: sourceChain, chains: chains, recipientAddress: recipientAddress, privateKey: options.privateKey, amountPerChain: options.amount }));
};
const BridgeInner = ({ sourceChain, chains, recipientAddress, privateKey, amountPerChain, }) => {
    const [executionOption, setExecutionOption] = useState(privateKey ? { type: 'privateKey', privateKey: privateKey } : null);
    return (_jsx(Box, { flexDirection: "column", gap: 1, children: _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, underline: true, children: "Bridge" }), _jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Text, { children: ["Source Chain: ", _jsx(Text, { color: "green", children: sourceChain.name })] }), _jsxs(Text, { children: ["Target Chains:", ' ', _jsx(Text, { color: "green", children: chains.map(chain => chain.name).join(', ') })] }), _jsxs(Text, { children: ["Recipient: ", _jsx(Text, { color: "green", children: recipientAddress })] }), _jsxs(Text, { children: ["Value:", ' ', _jsxs(Text, { color: "green", children: [formatEther(amountPerChain), " ETH \u00D7 ", chains.length, " chains =", ' ', formatEther(amountPerChain * BigInt(chains.length)), " ETH total"] })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { children: "Gas:" }), _jsx(GasEstimation, { sourceChain: sourceChain, chains: chains, recipientAddress: recipientAddress, amountPerChain: amountPerChain, accountToEstimateFrom: getAccountToEstimateFrom(sourceChain, privateKey) })] })] }), !executionOption && (_jsx(Box, { marginTop: 1, children: _jsx(ChooseExecutionOption, { label: '🌉 Ready to bridge!', onSubmit: setExecutionOption }) })), executionOption && (_jsx(SendStatus, { chains: chains, sourceChain: sourceChain, recipientAddress: recipientAddress, amountPerChain: amountPerChain, executionOption: executionOption }))] }) }));
};
const getAccountToEstimateFrom = (chain, privateKey) => {
    if (privateKey) {
        return privateKeyToAddress(privateKey);
    }
    if (chain.id === supersimL1.id || chain.sourceId === supersimL1.id) {
        return `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`;
    }
    return zeroAddress;
};
const SendStatus = ({ chains, sourceChain, recipientAddress, amountPerChain, executionOption, }) => {
    if (executionOption.type === 'privateKey') {
        return (_jsx(PrivateKeyExecution, { chains: chains, sourceChain: sourceChain, recipientAddress: recipientAddress, amountPerChain: amountPerChain, privateKey: executionOption.privateKey }));
    }
    if (executionOption.type === 'externalSigner') {
        return (_jsx(ExternalSignerExecution, { chains: chains, sourceChain: sourceChain, recipientAddress: recipientAddress, amountPerChain: amountPerChain }));
    }
    throw new Error('Invalid execution option');
};
const ExternalSignerExecution = ({ chains, sourceChain, recipientAddress, amountPerChain, }) => {
    const encodedData = encodeFunctionData(getContractWriteParams({
        chains,
        recipientAddress,
        amountPerChain,
    }));
    const { createTask, taskEntryById } = useTransactionTaskStore();
    const task = {
        chainId: sourceChain.id,
        to: '0xcA11bde05977b3631167028862bE2a173976CA11',
        data: encodedData,
        value: toHex(amountPerChain * BigInt(chains.length)),
    };
    const taskId = createTransactionTaskId(task);
    useEffect(() => {
        createTask(task);
    }, []);
    const transactionHash = taskEntryById[taskId]?.result?.type === 'success'
        ? taskEntryById[taskId]?.result?.hash
        : undefined;
    const { isLoading: isReceiptLoading } = useWaitForTransactionReceipt({
        hash: transactionHash,
        chainId: sourceChain.id,
    });
    if (!transactionHash) {
        return (_jsxs(Box, { gap: 1, children: [_jsx(Spinner, { label: "Waiting for signature..." }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { children: "Send the transaction at " }), _jsx(Text, { color: "cyan", bold: true, children: "http://localhost:3000" })] })] }));
    }
    if (isReceiptLoading && transactionHash) {
        return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Transaction sent:", ' ', getBlockExplorerTxHashLink(sourceChain, transactionHash)] }), _jsx(Spinner, { label: "Waiting for transaction confirmation" })] }));
    }
    return (_jsxs(Box, { gap: 1, children: [_jsx(Badge, { color: "green", children: "Bridged" }), _jsxs(Text, { children: [formatEther(amountPerChain * BigInt(chains.length)), " ETH successfully bridged"] }), _jsx(Text, { children: getBlockExplorerTxHashLink(sourceChain, transactionHash) })] }));
};
const PrivateKeyExecution = ({ chains, sourceChain, recipientAddress, amountPerChain, privateKey, }) => {
    const { writeContract, isPending, error, data: transactionHash, } = useWriteContract();
    const { isLoading: isReceiptLoading } = useWaitForTransactionReceipt({
        hash: transactionHash,
        chainId: sourceChain.id,
    });
    useEffect(() => {
        writeContract({
            chainId: sourceChain.id,
            ...getContractWriteParams({
                chains,
                recipientAddress,
                amountPerChain,
            }),
            account: privateKeyToAccount(privateKey),
        });
    }, []);
    if (error) {
        return _jsxs(Text, { children: ["Error bridging ETH: ", error.message] });
    }
    if (isPending || !transactionHash) {
        return _jsx(Spinner, { label: "Bridging ETH" });
    }
    if (isReceiptLoading) {
        return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["Transaction sent:", ' ', getBlockExplorerTxHashLink(sourceChain, transactionHash)] }), _jsx(Spinner, { label: "Waiting for transaction confirmation" })] }));
    }
    return (_jsxs(Box, { gap: 1, children: [_jsx(Badge, { color: "green", children: "Bridged" }), _jsxs(Text, { children: [formatEther(amountPerChain * BigInt(chains.length)), " ETH successfully bridged"] }), _jsx(Text, { children: getBlockExplorerTxHashLink(sourceChain, transactionHash) })] }));
};
const GasEstimation = ({ sourceChain, chains, recipientAddress, amountPerChain, accountToEstimateFrom, }) => {
    const contractWriteParams = getContractWriteParams({
        chains,
        recipientAddress,
        amountPerChain,
    });
    const encodedData = encodeFunctionData(getContractWriteParams({
        chains,
        recipientAddress,
        amountPerChain,
    }));
    const { data: estimatedGas, isLoading: isEstimateGasLoading, error: estimateGasError, } = useEstimateGas({
        chainId: sourceChain.id,
        to: '0xcA11bde05977b3631167028862bE2a173976CA11',
        data: encodedData,
        value: contractWriteParams.value,
        account: accountToEstimateFrom,
    });
    const { data: gasPrice, isLoading: isGasPriceLoading, error: gasPriceError, } = useGasPrice({
        chainId: sourceChain.id,
    });
    if (isEstimateGasLoading || isGasPriceLoading) {
        return _jsx(Spinner, { label: "Estimating..." });
    }
    if (estimateGasError || gasPriceError) {
        console.error(estimateGasError, gasPriceError);
        return _jsx(Text, { color: "red", children: "Error estimating gas" });
    }
    if (estimatedGas === undefined || gasPrice === undefined) {
        // Invariant: we should always have gas data
        throw new Error('No gas data');
    }
    const fees = estimatedGas * gasPrice;
    return (_jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: estimatedGas.toLocaleString() }), " \u00D7", ' ', _jsxs(Text, { color: "cyan", children: [Number(formatGwei(gasPrice)).toFixed(2), " gwei"] }), " =", ' ', _jsxs(Text, { color: "green", children: [Number(formatEther(fees)).toFixed(5), " ETH"] })] }));
};
const getContractWriteParams = ({ chains, recipientAddress, amountPerChain, }) => {
    return {
        abi: multicall3Abi,
        functionName: 'aggregate3Value',
        address: '0xcA11bde05977b3631167028862bE2a173976CA11',
        args: [
            chains.map(chain => {
                const l1StandardBridgeAddress = 
                // @ts-expect-error
                chain.contracts?.l1StandardBridge?.[chain.sourceId]?.address;
                if (!l1StandardBridgeAddress) {
                    // Invariant: we should always have a l1StandardBridge defined (see chains.ts)
                    throw new Error('l1StandardBridge not found');
                }
                return {
                    target: l1StandardBridgeAddress,
                    allowFailure: false,
                    callData: encodeFunctionData({
                        abi: l1StandardBridgeAbi,
                        functionName: 'bridgeETHTo',
                        args: [recipientAddress, 1000000, toHex('')],
                    }),
                    value: amountPerChain,
                };
            }),
        ],
        value: amountPerChain * BigInt(chains.length),
    };
};
export default BridgeEntrypoint;
export const options = zodBridgeParams;
