"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AcidTest = exports.outputScriptTypes = exports.inputScriptTypes = exports.txFormats = exports.signStages = void 0;
const BitGoPsbt_js_1 = require("../fixedScriptWallet/BitGoPsbt.js");
const ZcashBitGoPsbt_js_1 = require("../fixedScriptWallet/ZcashBitGoPsbt.js");
const ecpair_js_1 = require("../ecpair.js");
const transaction_js_1 = require("../transaction.js");
const index_js_1 = require("../fixedScriptWallet/index.js");
Object.defineProperty(exports, "inputScriptTypes", { enumerable: true, get: function () { return index_js_1.inputScriptTypes; } });
Object.defineProperty(exports, "outputScriptTypes", { enumerable: true, get: function () { return index_js_1.outputScriptTypes; } });
const coinName_js_1 = require("../coinName.js");
const keys_js_1 = require("./keys.js");
exports.signStages = ["unsigned", "halfsigned", "fullsigned"];
exports.txFormats = ["psbt", "psbt-lite"];
/** Map InputScriptType to the OutputScriptType used for chain code derivation */
function inputScriptTypeToOutputScriptType(scriptType) {
    switch (scriptType) {
        case "p2sh":
        case "p2shP2wsh":
        case "p2wsh":
        case "p2trLegacy":
            return scriptType;
        case "p2shP2pk":
            return "p2sh";
        case "p2trMusig2ScriptPath":
        case "p2trMusig2KeyPath":
            return "p2trMusig2";
    }
}
/**
 * Creates a valid PSBT with as many features as possible (kitchen sink).
 *
 * - Inputs:
 *   - All wallet script types supported by the network
 *   - A p2shP2pk input (for replay protection)
 * - Outputs:
 *   - All wallet script types supported by the network
 *   - A p2sh output with derivation info of a different wallet
 *   - A p2sh output with no derivation info (external output)
 *   - An OP_RETURN output
 *
 * Signature stages:
 * - unsigned: No signatures
 * - halfsigned: One signature per input (user key)
 * - fullsigned: Two signatures per input (user + bitgo)
 *
 * Transaction formats:
 * - psbt: Full PSBT with non_witness_utxo
 * - psbt-lite: Only witness_utxo (no non_witness_utxo)
 */
class AcidTest {
    coin;
    signStage;
    txFormat;
    rootWalletKeys;
    otherWalletKeys;
    inputs;
    outputs;
    // Store private keys for signing
    userXprv;
    backupXprv;
    bitgoXprv;
    constructor(coin, signStage, txFormat, rootWalletKeys, otherWalletKeys, inputs, outputs, xprvTriple) {
        this.coin = coin;
        this.signStage = signStage;
        this.txFormat = txFormat;
        this.rootWalletKeys = rootWalletKeys;
        this.otherWalletKeys = otherWalletKeys;
        this.inputs = inputs;
        this.outputs = outputs;
        this.userXprv = xprvTriple[0];
        this.backupXprv = xprvTriple[1];
        this.bitgoXprv = xprvTriple[2];
    }
    /**
     * Create an AcidTest with specific configuration
     */
    static withConfig(coin, signStage, txFormat, suiteConfig = {}) {
        const rootWalletKeys = (0, keys_js_1.getDefaultWalletKeys)();
        const otherWalletKeys = (0, keys_js_1.getWalletKeysForSeed)("too many secrets");
        // Filter inputs based on network support
        const inputs = index_js_1.inputScriptTypes
            .filter((scriptType) => {
            // p2shP2pk is always supported (single-sig replay protection)
            if (scriptType === "p2shP2pk")
                return true;
            // Map input script types to output script types for support check
            if (scriptType === "p2trMusig2KeyPath" || scriptType === "p2trMusig2ScriptPath") {
                return (0, index_js_1.supportsScriptType)(coin, "p2trMusig2");
            }
            return (0, index_js_1.supportsScriptType)(coin, scriptType);
        })
            .filter((scriptType) => (suiteConfig.includeP2trMusig2ScriptPath ?? false) ||
            scriptType !== "p2trMusig2ScriptPath")
            .map((scriptType, index) => ({
            scriptType,
            value: BigInt(10000 + index * 10000), // Deterministic amounts
        }));
        // Filter outputs based on network support
        const outputs = index_js_1.outputScriptTypes
            .filter((scriptType) => (0, index_js_1.supportsScriptType)(coin, scriptType))
            .map((scriptType, index) => ({
            scriptType,
            value: BigInt(900 + index * 100), // Deterministic amounts
        }));
        // Test other wallet output (with derivation info)
        outputs.push({ scriptType: "p2sh", value: BigInt(800), walletKeys: otherWalletKeys });
        // Test non-wallet output (no derivation info)
        outputs.push({ scriptType: "p2sh", value: BigInt(700), walletKeys: null });
        // Test OP_RETURN output
        outputs.push({ opReturn: "setec astronomy", value: BigInt(0) });
        // Get private keys for signing
        const xprvTriple = (0, keys_js_1.getKeyTriple)("default");
        return new AcidTest(coin, signStage, txFormat, rootWalletKeys, otherWalletKeys, inputs, outputs, xprvTriple);
    }
    /**
     * Get a human-readable name for this test configuration
     */
    get name() {
        return `${this.coin} ${this.signStage} ${this.txFormat}`;
    }
    /**
     * Get the BIP32 user key for replay protection (p2shP2pk)
     */
    getReplayProtectionKey() {
        return this.rootWalletKeys.userKey();
    }
    /**
     * Create the actual PSBT with all inputs and outputs
     */
    createPsbt() {
        // Use ZcashBitGoPsbt for Zcash networks
        const isZcash = this.coin === "zec" || this.coin === "tzec";
        const psbt = isZcash
            ? ZcashBitGoPsbt_js_1.ZcashBitGoPsbt.createEmpty(this.coin, this.rootWalletKeys, {
                // Sapling activation height: mainnet=419200, testnet=280000
                blockHeight: this.coin === "zec" ? 419200 : 280000,
            })
            : BitGoPsbt_js_1.BitGoPsbt.createEmpty(this.coin, this.rootWalletKeys, {
                version: 2,
                lockTime: 0,
            });
        // Build a fake previous transaction for non_witness_utxo (psbt format)
        const usePrevTx = this.txFormat === "psbt" && !isZcash;
        const buildPrevTx = (vout, script, value) => {
            if (!usePrevTx)
                return undefined;
            const tx = transaction_js_1.Transaction.create();
            tx.addInput("0".repeat(64), 0xffffffff);
            for (let i = 0; i < vout; i++) {
                tx.addOutput(new Uint8Array(0), 0n);
            }
            tx.addOutput(script, value);
            return tx.toBytes();
        };
        // Add inputs with deterministic outpoints
        this.inputs.forEach((input, index) => {
            const walletKeys = input.walletKeys ?? this.rootWalletKeys;
            const outpoint = { txid: "0".repeat(64), vout: index, value: input.value };
            // scriptId variant: caller provides explicit chain + index
            if (input.scriptId) {
                const script = (0, index_js_1.outputScript)(walletKeys, input.scriptId.chain, input.scriptId.index, this.coin);
                psbt.addWalletInput({ ...outpoint, prevTx: buildPrevTx(index, script, input.value) }, walletKeys, { scriptId: input.scriptId, signPath: { signer: "user", cosigner: "bitgo" } });
                return;
            }
            const scriptType = input.scriptType ?? "p2sh";
            if (scriptType === "p2shP2pk") {
                const ecpair = ecpair_js_1.ECPair.fromPublicKey(this.getReplayProtectionKey().publicKey);
                const script = (0, index_js_1.p2shP2pkOutputScript)(ecpair.publicKey);
                psbt.addReplayProtectionInput({ ...outpoint, prevTx: buildPrevTx(index, script, input.value) }, ecpair);
                return;
            }
            const scriptId = {
                chain: index_js_1.ChainCode.value(inputScriptTypeToOutputScriptType(scriptType), "external"),
                index: input.index ?? index,
            };
            const signPath = scriptType === "p2trMusig2ScriptPath"
                ? { signer: "user", cosigner: "backup" }
                : { signer: "user", cosigner: "bitgo" };
            const script = (0, index_js_1.outputScript)(walletKeys, scriptId.chain, scriptId.index, this.coin);
            psbt.addWalletInput({ ...outpoint, prevTx: buildPrevTx(index, script, input.value) }, walletKeys, { scriptId, signPath });
        });
        // Add outputs
        this.outputs.forEach((output, index) => {
            if (output.opReturn !== undefined) {
                // OP_RETURN output
                const data = new TextEncoder().encode(output.opReturn);
                const script = (0, index_js_1.createOpReturnScript)(data);
                psbt.addOutput(script, output.value);
            }
            else if (output.address !== undefined) {
                // Address-based output
                psbt.addOutput(output.address, output.value);
            }
            else if (output.script !== undefined) {
                // Raw script output
                psbt.addOutput(output.script, output.value);
            }
            else {
                // Wallet output: resolve scriptId from scriptType or explicit scriptId
                const scriptId = output.scriptId ?? {
                    chain: output.scriptType ? index_js_1.ChainCode.value(output.scriptType, "external") : 0,
                    index: output.index ?? index,
                };
                if (output.walletKeys === null) {
                    // External output (no wallet keys, no bip32 derivation)
                    // Use high index for external outputs if not specified
                    const externalScriptId = output.scriptId ?? {
                        chain: scriptId.chain,
                        index: output.index ?? 1000 + index,
                    };
                    const script = (0, index_js_1.outputScript)(this.rootWalletKeys, externalScriptId.chain, externalScriptId.index, this.coin);
                    psbt.addOutput(script, output.value);
                }
                else {
                    // Wallet output (with or without different wallet keys)
                    const walletKeys = output.walletKeys ?? this.rootWalletKeys;
                    psbt.addWalletOutput(walletKeys, {
                        chain: scriptId.chain,
                        index: scriptId.index,
                        value: output.value,
                    });
                }
            }
        });
        // Apply signing based on stage
        if (this.signStage !== "unsigned") {
            this.signPsbt(psbt);
        }
        return psbt;
    }
    /**
     * Sign the PSBT according to the sign stage
     */
    signPsbt(psbt) {
        // Use private keys stored in constructor
        const userKey = this.userXprv;
        const backupKey = this.backupXprv;
        const bitgoKey = this.bitgoXprv;
        // Generate MuSig2 nonces for user if needed
        const hasMusig2Inputs = this.inputs.some((input) => input.scriptType === "p2trMusig2KeyPath" || input.scriptType === "p2trMusig2ScriptPath");
        if (hasMusig2Inputs) {
            if (this.coin === "zec" || this.coin === "tzec") {
                throw new Error("Zcash does not support MuSig2/Taproot inputs");
            }
            // MuSig2 requires ALL participant nonces before ANY signing.
            // Generate nonces directly on the same PSBT for each participant key.
            psbt.generateMusig2Nonces(userKey);
            const hasKeyPath = this.inputs.some((input) => input.scriptType === "p2trMusig2KeyPath");
            const hasScriptPath = this.inputs.some((input) => input.scriptType === "p2trMusig2ScriptPath");
            // Key path uses user+bitgo, script path uses user+backup.
            // generateMusig2Nonces fails if the key isn't a participant in any musig2 input,
            // so we only call it for keys that match.
            if (hasKeyPath) {
                psbt.generateMusig2Nonces(bitgoKey);
            }
            if (hasScriptPath) {
                psbt.generateMusig2Nonces(backupKey);
            }
        }
        // Sign all wallet inputs with user key (bulk - more efficient)
        psbt.sign(userKey);
        // Sign replay protection inputs with raw private key
        const hasReplayProtection = this.inputs.some((input) => input.scriptType === "p2shP2pk");
        if (hasReplayProtection) {
            if (!userKey.privateKey) {
                throw new Error("User key must have private key for signing replay protection inputs");
            }
            psbt.sign(userKey.privateKey);
        }
        // For fullsigned, sign with cosigner
        if (this.signStage === "fullsigned") {
            const hasScriptPath = this.inputs.some((input) => input.scriptType === "p2trMusig2ScriptPath");
            if (hasScriptPath) {
                // Mixed case: script path uses backup, others use bitgo
                // Need per-input signing (slow) to handle different cosigners
                this.inputs.forEach((input, index) => {
                    if (input.scriptType === "p2shP2pk") {
                        // Replay protection is single-sig, already fully signed
                        return;
                    }
                    if (input.scriptType === "p2trMusig2ScriptPath") {
                        psbt.signInput(index, backupKey);
                    }
                    else {
                        psbt.signInput(index, bitgoKey);
                    }
                });
            }
            else {
                // No script path - can use bulk signing with bitgo (fast)
                psbt.sign(bitgoKey);
            }
        }
    }
    /**
     * Generate test suite for all networks, sign stages, and tx formats
     */
    static forAllNetworksSignStagesTxFormats(suiteConfig = {}) {
        return coinName_js_1.coinNames
            .filter((network) => (0, coinName_js_1.isMainnet)(network) && network !== "bsv")
            .flatMap((network) => exports.signStages.flatMap((signStage) => exports.txFormats.map((txFormat) => AcidTest.withConfig(network, signStage, txFormat, suiteConfig))));
    }
}
exports.AcidTest = AcidTest;
