"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const LockingScript_js_1 = __importDefault(require("../LockingScript.js"));
const UnlockingScript_js_1 = __importDefault(require("../UnlockingScript.js"));
const OP_js_1 = __importDefault(require("../OP.js"));
const index_js_1 = require("../../primitives/index.js");
const utils_js_1 = require("../../primitives/utils.js");
const SignatureUtils_js_1 = require("./SignatureUtils.js");
/**
 * For a given piece of data to push onto the stack in script, creates the correct minimally-encoded script chunk,
 * including the correct push operation.
 *
 */
const createMinimallyEncodedScriptChunk = (data) => {
    if (data.length === 0) {
        // Could have used OP_0.
        return { op: 0 };
    }
    if (data.length === 1 && data[0] === 0) {
        // Could have used OP_0.
        return { op: 0 };
    }
    if (data.length === 1 && data[0] > 0 && data[0] <= 16) {
        // Could have used OP_0 .. OP_16.
        return { op: 0x50 + data[0] };
    }
    if (data.length === 1 && data[0] === 0x81) {
        // Could have used OP_1NEGATE.
        return { op: 0x4f };
    }
    if (data.length <= 75) {
        // Could have used a direct push (opcode indicating number of bytes
        // pushed + those bytes).
        return { op: data.length, data };
    }
    if (data.length <= 255) {
        // Could have used OP_PUSHDATA.
        return { op: 0x4c, data };
    }
    if (data.length <= 65535) {
        // Could have used OP_PUSHDATA2.
        return { op: 0x4d, data };
    }
    return { op: 0x4e, data };
};
class PushDrop {
    /**
     * Decodes a PushDrop script back into its token fields and the locking public key. If a signature was present, it will be the last field returned.
     * @param script PushDrop script to decode back into token fields
     * @param lockPosition Where the locking public key is positioned in the script ('before' = at start, 'after' = at end after DROP operations)
     * @returns An object containing PushDrop token fields and the locking public key. If a signature was included, it will be the last field.
     */
    static decode(script, lockPosition = 'before') {
        let lockingPublicKey;
        let startIndex;
        if (lockPosition === 'before') {
            lockingPublicKey = index_js_1.PublicKey.fromString(index_js_1.Utils.toHex((0, utils_js_1.verifyNotNull)(script.chunks[0].data, 'script.chunks[0].data must have value')));
            startIndex = 2;
        }
        else {
            // lockPosition === 'after'
            // Find the public key at the end (after the last DROP/2DROP, before OP_CHECKSIG)
            const lastChunkIndex = script.chunks.length - 1;
            if (script.chunks[lastChunkIndex].op !== OP_js_1.default.OP_CHECKSIG) {
                throw new Error('Expected OP_CHECKSIG at the end of the script');
            }
            lockingPublicKey = index_js_1.PublicKey.fromString(index_js_1.Utils.toHex((0, utils_js_1.verifyNotNull)(script.chunks[lastChunkIndex - 1].data, 'public key chunk data must have value')));
            startIndex = 0;
        }
        const fields = [];
        for (let i = startIndex; i < script.chunks.length; i++) {
            const nextOpcode = script.chunks[i + 1]?.op;
            let chunk = script.chunks[i].data ?? [];
            if (chunk.length === 0) {
                if (script.chunks[i].op >= 80 && script.chunks[i].op <= 95) {
                    chunk = [script.chunks[i].op - 80];
                }
                else if (script.chunks[i].op === 0) {
                    chunk = [0];
                }
                else if (script.chunks[i].op === 0x4f) {
                    chunk = [0x81];
                }
            }
            fields.push(chunk);
            // If the next value is DROP or 2DROP then this is the final field
            if (nextOpcode === OP_js_1.default.OP_DROP || nextOpcode === OP_js_1.default.OP_2DROP) {
                break;
            }
        }
        return {
            fields,
            lockingPublicKey
        };
    }
    /**
     * Constructs a new instance of the PushDrop class.
     *
     * @param {WalletInterface} wallet - The wallet interface used for creating signatures and accessing public keys.
     * @param {string} originator — The originator to use with Wallet requests
     */
    constructor(wallet, originator) {
        this.wallet = wallet;
        this.originator = originator;
    }
    /**
     * Creates a PushDrop locking script with arbitrary data fields and a public key lock.
     *
     * @param {number[][]} fields - The token fields to include in the locking script.
     * @param {WalletProtocol} protocolID - The protocol ID to use.
     * @param {string} keyID - The key ID to use.
     * @param {string} counterparty - The counterparty involved in the transaction, "self" or "anyone".
     * @param {boolean} [forSelf=false] - Flag indicating if the lock is for the creator (default no).
     * @param {boolean} [includeSignature=true] - Flag indicating if a signature should be included in the script (default yes).
     * @returns {Promise<LockingScript>} The generated PushDrop locking script.
     */
    async lock(fields, protocolID, keyID, counterparty, forSelf = false, includeSignature = true, lockPosition = 'before') {
        const { publicKey } = await this.wallet.getPublicKey({
            protocolID,
            keyID,
            counterparty,
            forSelf
        }, this.originator);
        const lockChunks = [];
        const pushDropChunks = [];
        lockChunks.push({
            op: publicKey.length / 2,
            data: index_js_1.Utils.toArray(publicKey, 'hex')
        });
        lockChunks.push({ op: OP_js_1.default.OP_CHECKSIG });
        if (includeSignature) {
            const dataToSign = fields.flat();
            const { signature } = await this.wallet.createSignature({
                data: dataToSign,
                protocolID,
                keyID,
                counterparty
            }, this.originator);
            fields.push(signature);
        }
        for (const field of fields) {
            pushDropChunks.push(createMinimallyEncodedScriptChunk(field));
        }
        let notYetDropped = fields.length;
        while (notYetDropped > 1) {
            pushDropChunks.push({ op: OP_js_1.default.OP_2DROP });
            notYetDropped -= 2;
        }
        if (notYetDropped !== 0) {
            pushDropChunks.push({ op: OP_js_1.default.OP_DROP });
        }
        if (lockPosition === 'before') {
            return new LockingScript_js_1.default([...lockChunks, ...pushDropChunks]);
        }
        else {
            return new LockingScript_js_1.default([...pushDropChunks, ...lockChunks]);
        }
    }
    /**
     * Creates an unlocking script for spending a PushDrop token output.
     *
     * @param {WalletProtocol} protocolID - The protocol ID to use.
     * @param {string} keyID - The key ID to use.
     * @param {string} counterparty - The counterparty involved in the transaction, "self" or "anyone".
     * @param {string} [sourceTXID] - The TXID of the source transaction.
     * @param {number} [sourceSatoshis] - The number of satoshis in the source output.
     * @param {LockingScript} [lockingScript] - The locking script of the source output.
     * @param {'all' | 'none' | 'single'} [signOutputs='all'] - Specifies which outputs to sign.
     * @param {boolean} [anyoneCanPay=false] - Specifies if the anyone-can-pay flag is set.
     * @returns {Object} An object containing functions to sign the transaction and estimate the script length.
     */
    unlock(protocolID, keyID, counterparty, signOutputs = 'all', anyoneCanPay = false, sourceSatoshis, lockingScript) {
        return {
            sign: async (tx, inputIndex) => {
                const signatureScope = (0, SignatureUtils_js_1.computeSignatureScope)(signOutputs, anyoneCanPay);
                const resolved = (0, SignatureUtils_js_1.resolveSourceDetails)(tx, inputIndex, sourceSatoshis, lockingScript);
                sourceSatoshis = resolved.sourceSatoshis;
                lockingScript = resolved.lockingScript;
                const preimage = (0, SignatureUtils_js_1.formatPreimage)({
                    tx, inputIndex, signatureScope,
                    sourceTXID: resolved.sourceTXID, sourceSatoshis: resolved.sourceSatoshis,
                    lockingScript: resolved.lockingScript, otherInputs: resolved.otherInputs,
                    inputSequence: tx.inputs[inputIndex].sequence ?? 0xffffffff
                });
                const preimageHash = index_js_1.Hash.sha256(preimage);
                const { signature: bareSignature } = await this.wallet.createSignature({
                    data: preimageHash,
                    protocolID,
                    keyID,
                    counterparty
                }, this.originator);
                const signature = index_js_1.Signature.fromDER([...bareSignature]);
                const txSignature = new index_js_1.TransactionSignature(signature.r, signature.s, signatureScope);
                const sigForScript = txSignature.toChecksigFormat();
                return new UnlockingScript_js_1.default([
                    { op: sigForScript.length, data: sigForScript }
                ]);
            },
            estimateLength: async () => 73
        };
    }
}
exports.default = PushDrop;
//# sourceMappingURL=PushDrop.js.map