"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrderOfferContainer = exports.OrderOffer = void 0;
const bufio_1 = require("@node-dlc/bufio");
const MessageType_1 = require("../MessageType");
const deserializeTlv_1 = require("../serialize/deserializeTlv");
const getTlv_1 = require("../serialize/getTlv");
const util_1 = require("../util");
const BatchFundingGroup_1 = require("./BatchFundingGroup");
const ContractInfo_1 = require("./ContractInfo");
const OrderIrcInfo_1 = require("./OrderIrcInfo");
const OrderMetadata_1 = require("./OrderMetadata");
const OrderPositionInfo_1 = require("./OrderPositionInfo");
const LOCKTIME_THRESHOLD = 500000000;
/**
 * OrderOffer message contains information about a node and indicates its
 * desire to enter into a new contract. This is the first step toward
 * order negotiation. This is a simpler message than DlcOffer.
 */
class OrderOffer {
    constructor() {
        /**
         * The type for order_offer message. order_offer = 62770
         */
        this.type = OrderOffer.type;
        // New fields as per dlcspecs PR #163
        this.protocolVersion = MessageType_1.PROTOCOL_VERSION; // Default to current protocol version
    }
    /**
     * Creates an OrderOffer from JSON data
     * @param json JSON object representing an order offer
     */
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
    static fromJSON(json) {
        const instance = new OrderOffer();
        // Basic fields with field name variations
        instance.protocolVersion =
            json.protocolVersion || json.protocol_version || MessageType_1.PROTOCOL_VERSION;
        instance.contractFlags = Buffer.from(json.contractFlags || json.contract_flags || '00', 'hex');
        instance.chainHash = Buffer.from(json.chainHash || json.chain_hash, 'hex');
        instance.temporaryContractId = Buffer.from(json.temporaryContractId || json.temporary_contract_id, 'hex');
        // Use toBigInt helper to handle BigInt values from json-bigint
        instance.offerCollateral = (0, util_1.toBigInt)(json.offerCollateral ||
            json.offerCollateralSatoshis ||
            json.offer_collateral);
        instance.feeRatePerVb = (0, util_1.toBigInt)(json.feeRatePerVb || json.fee_rate_per_vb);
        instance.cetLocktime = json.cetLocktime || json.cet_locktime || 0;
        instance.refundLocktime = json.refundLocktime || json.refund_locktime || 0;
        // Use ContractInfo.fromJSON() - proper delegation
        instance.contractInfo = ContractInfo_1.ContractInfo.fromJSON(json.contractInfo || json.contract_info);
        return instance;
    }
    /**
     * Deserializes an order_offer message
     * @param buf
     */
    static deserialize(buf) {
        const instance = new OrderOffer();
        const reader = new bufio_1.BufferReader(buf);
        const type = reader.readUInt16BE(); // read type
        // Validate type matches expected OrderOffer type
        if (type !== MessageType_1.MessageType.OrderOffer) {
            throw new Error(`Invalid message type. Expected ${MessageType_1.MessageType.OrderOffer}, got ${type}`);
        }
        // BACKWARD COMPATIBILITY: Detect old vs new format
        const nextBytes = reader.buffer.subarray(reader.position, reader.position + 5);
        const possibleProtocolVersion = nextBytes.readUInt32BE(0);
        const possibleContractFlags = nextBytes.readUInt8(4);
        // Heuristic: protocol_version should be 1, contract_flags should be 0
        const isNewFormat = possibleProtocolVersion >= 1 &&
            possibleProtocolVersion <= 10 &&
            possibleContractFlags === 0;
        if (isNewFormat) {
            // New format with protocol_version
            instance.protocolVersion = reader.readUInt32BE();
            instance.contractFlags = reader.readBytes(1);
        }
        else {
            // Old format without protocol_version
            instance.protocolVersion = 1; // Default to version 1
            instance.contractFlags = reader.readBytes(1);
        }
        instance.chainHash = reader.readBytes(32);
        instance.temporaryContractId = reader.readBytes(32);
        // ContractInfo is serialized as sibling type in dlcspecs PR #163 format
        instance.contractInfo = ContractInfo_1.ContractInfo.deserialize(reader.buffer.subarray(reader.position));
        // Skip past the ContractInfo we just read
        const contractInfoLength = instance.contractInfo.serialize().length;
        reader.position += contractInfoLength;
        instance.offerCollateral = reader.readUInt64BE();
        instance.feeRatePerVb = reader.readUInt64BE();
        instance.cetLocktime = reader.readUInt32BE();
        instance.refundLocktime = reader.readUInt32BE();
        // Parse TLV stream as per dlcspecs PR #163
        while (!reader.eof) {
            const buf = (0, getTlv_1.getTlv)(reader);
            const tlvReader = new bufio_1.BufferReader(buf);
            const { type } = (0, deserializeTlv_1.deserializeTlv)(tlvReader);
            switch (Number(type)) {
                case MessageType_1.MessageType.OrderMetadataV0:
                    instance.metadata = OrderMetadata_1.OrderMetadataV0.deserialize(buf);
                    break;
                case MessageType_1.MessageType.OrderIrcInfoV0:
                    instance.ircInfo = OrderIrcInfo_1.OrderIrcInfoV0.deserialize(buf);
                    break;
                case MessageType_1.MessageType.OrderPositionInfo:
                    instance.positionInfo = OrderPositionInfo_1.OrderPositionInfo.deserialize(buf);
                    break;
                case MessageType_1.MessageType.BatchFundingGroup:
                    if (!instance.batchFundingGroups) {
                        instance.batchFundingGroups = [];
                    }
                    instance.batchFundingGroups.push(BatchFundingGroup_1.BatchFundingGroup.deserialize(buf));
                    break;
                default:
                    // Store unknown TLVs for future compatibility
                    if (!instance.unknownTlvs) {
                        instance.unknownTlvs = [];
                    }
                    instance.unknownTlvs.push({ type: Number(type), data: buf });
                    break;
            }
        }
        return instance;
    }
    // Legacy property for backward compatibility
    get offerCollateralSatoshis() {
        return this.offerCollateral;
    }
    set offerCollateralSatoshis(value) {
        this.offerCollateral = value;
    }
    validate() {
        // 1. Type is set automatically in class
        // 2. protocol_version validation
        if (this.protocolVersion !== MessageType_1.PROTOCOL_VERSION) {
            throw new Error(`Unsupported protocol version: ${this.protocolVersion}, expected: ${MessageType_1.PROTOCOL_VERSION}`);
        }
        // 3. temporary_contract_id validation
        if (!this.temporaryContractId || this.temporaryContractId.length !== 32) {
            throw new Error('temporaryContractId must be 32 bytes');
        }
        // 4. contract_flags field is ignored
        // 5. chain_hash must be validated as input by end user
        // 6. offer_collateral must be greater than or equal to 1000
        if (this.offerCollateral < 1000) {
            throw new Error('offer_collateral must be greater than or equal to 1000');
        }
        if (this.cetLocktime < 0) {
            throw new Error('cet_locktime must be greater than or equal to 0');
        }
        if (this.refundLocktime < 0) {
            throw new Error('refund_locktime must be greater than or equal to 0');
        }
        // 7. cet_locktime and refund_locktime must either both be unix timestamps, or both be block heights.
        if (!((this.cetLocktime < LOCKTIME_THRESHOLD &&
            this.refundLocktime < LOCKTIME_THRESHOLD) ||
            (this.cetLocktime >= LOCKTIME_THRESHOLD &&
                this.refundLocktime >= LOCKTIME_THRESHOLD))) {
            throw new Error('cetLocktime and refundLocktime must be in same units');
        }
        // 8. cetLocktime must be less than refundLocktime
        if (this.cetLocktime >= this.refundLocktime) {
            throw new Error('cetLocktime must be less than refundLocktime');
        }
        // validate contractInfo
        this.contractInfo.validate();
        // totalCollateral should be > offerCollateral (logical validation)
        if (this.contractInfo.getTotalCollateral() <= this.offerCollateral) {
            throw new Error('totalCollateral should be greater than offerCollateral');
        }
    }
    /**
     * Converts order_offer to JSON
     */
    toJSON() {
        const tlvs = [];
        if (this.metadata)
            tlvs.push(this.metadata.toJSON());
        if (this.ircInfo)
            tlvs.push(this.ircInfo.toJSON());
        if (this.positionInfo)
            tlvs.push(this.positionInfo.toJSON());
        if (this.batchFundingGroups)
            this.batchFundingGroups.forEach((fundingInfo) => tlvs.push(fundingInfo.toJSON()));
        // Include unknown TLVs for debugging
        if (this.unknownTlvs) {
            this.unknownTlvs.forEach((tlv) => tlvs.push({ type: tlv.type, data: tlv.data.toString('hex') }));
        }
        return {
            type: this.type,
            protocolVersion: this.protocolVersion,
            temporaryContractId: this.temporaryContractId.toString('hex'),
            contractFlags: Number(this.contractFlags[0]),
            chainHash: this.chainHash.toString('hex'),
            contractInfo: this.contractInfo.toJSON(),
            offerCollateral: (0, util_1.bigIntToNumber)(this.offerCollateral),
            offerCollateralSatoshis: (0, util_1.bigIntToNumber)(this.offerCollateral),
            feeRatePerVb: (0, util_1.bigIntToNumber)(this.feeRatePerVb),
            cetLocktime: this.cetLocktime,
            refundLocktime: this.refundLocktime,
            tlvs,
        };
    }
    /**
     * Serializes the order_offer message into a Buffer
     */
    serialize() {
        const writer = new bufio_1.BufferWriter();
        writer.writeUInt16BE(this.type);
        // New fields as per dlcspecs PR #163
        writer.writeUInt32BE(this.protocolVersion);
        writer.writeBytes(this.contractFlags);
        writer.writeBytes(this.chainHash);
        writer.writeBytes(this.temporaryContractId); // New field
        writer.writeBytes(this.contractInfo.serialize());
        writer.writeUInt64BE(this.offerCollateral);
        writer.writeUInt64BE(this.feeRatePerVb);
        writer.writeUInt32BE(this.cetLocktime);
        writer.writeUInt32BE(this.refundLocktime);
        // TLV stream as per dlcspecs PR #163
        if (this.metadata)
            writer.writeBytes(this.metadata.serialize());
        if (this.ircInfo)
            writer.writeBytes(this.ircInfo.serialize());
        if (this.positionInfo)
            writer.writeBytes(this.positionInfo.serialize());
        if (this.batchFundingGroups)
            this.batchFundingGroups.forEach((fundingInfo) => writer.writeBytes(fundingInfo.serialize()));
        // Write unknown TLVs for forward compatibility
        if (this.unknownTlvs) {
            this.unknownTlvs.forEach((tlv) => {
                writer.writeBytes(tlv.data);
            });
        }
        return writer.toBuffer();
    }
}
exports.OrderOffer = OrderOffer;
OrderOffer.type = MessageType_1.MessageType.OrderOffer;
class OrderOfferContainer {
    constructor() {
        this.offers = [];
    }
    /**
     * Adds an OrderOffer to the container.
     * @param offer The OrderOffer to add.
     */
    addOffer(offer) {
        this.offers.push(offer);
    }
    /**
     * Returns all OrderOffers in the container.
     * @returns An array of OrderOffer instances.
     */
    getOffers() {
        return this.offers;
    }
    /**
     * Serializes all OrderOffers in the container to a Buffer.
     * @returns A Buffer containing the serialized OrderOffers.
     */
    serialize() {
        const writer = new bufio_1.BufferWriter();
        // Write the number of offers in the container first.
        writer.writeBigSize(this.offers.length);
        // Serialize each offer and write it.
        this.offers.forEach((offer) => {
            const serializedOffer = offer.serialize();
            // Optionally, write the length of the serialized offer for easier deserialization.
            writer.writeBigSize(serializedOffer.length);
            writer.writeBytes(serializedOffer);
        });
        return writer.toBuffer();
    }
    /**
     * Deserializes a Buffer into an OrderOfferContainer with OrderOffers.
     * @param buf The Buffer to deserialize.
     * @returns An OrderOfferContainer instance.
     */
    static deserialize(buf) {
        const reader = new bufio_1.BufferReader(buf);
        const container = new OrderOfferContainer();
        const offersCount = reader.readBigSize();
        for (let i = 0; i < offersCount; i++) {
            // Optionally, read the length of the serialized offer if it was written during serialization.
            const offerLength = reader.readBigSize();
            const offerBuf = reader.readBytes(Number(offerLength));
            const offer = OrderOffer.deserialize(offerBuf);
            container.addOffer(offer);
        }
        return container;
    }
}
exports.OrderOfferContainer = OrderOfferContainer;
//# sourceMappingURL=OrderOffer.js.map