import { BaseCoreSDK } from "./../mixins/base-core-sdk";
import * as erc20 from "../erc20";
import * as erc721 from "../erc721";
import * as erc1155 from "../erc1155";
import * as offers from ".";
import * as accounts from "../accounts";
import * as orchestration from "../orchestration";
import * as groups from "../groups";
import { TokenType, EvaluationMethod, GatingType } from "@bosonprotocol/common";
import groupBy from "lodash/groupBy";
import { BigNumber } from "@ethersproject/bignumber";
import { getValueFromLogs, getValuesFromLogs } from "../utils/logs";
import { TokenInfoManager } from "../utils/tokenInfoManager";
import { batchTasks } from "../utils/promises";
export class OfferMixin extends BaseCoreSDK {
    // Implementation
    async createOffer(offerToCreate, overrides = {}) {
        const { returnTxInfo } = overrides;
        const offerArgs = {
            offerToCreate,
            web3Lib: this._web3Lib,
            theGraphStorage: this._theGraphStorage,
            metadataStorage: this._metadataStorage,
            subgraphUrl: this._subgraphUrl,
            contractAddress: overrides.contractAddress || this._protocolDiamond,
            txRequest: overrides.txRequest
        };
        if (returnTxInfo === true) {
            return offers.handler.createOffer({
                ...offerArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.createOffer({
                ...offerArgs,
                returnTxInfo: false
            });
        }
    }
    // Implementation
    async createOfferBatch(offersToCreate, overrides = {}) {
        const { returnTxInfo } = overrides;
        const batchArgs = {
            offersToCreate,
            web3Lib: this._web3Lib,
            theGraphStorage: this._theGraphStorage,
            metadataStorage: this._metadataStorage,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.createOfferBatch({
                ...batchArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.createOfferBatch({
                ...batchArgs,
                returnTxInfo: false
            });
        }
    }
    /**
     * Utility method to retrieve the created `offerId` from logs after calling `createOffer`
     * or `createOfferAndSeller`.
     * @param logs - Logs to search in.
     * @returns Created offer id.
     */
    getCreatedOfferIdFromLogs(logs) {
        const offerId = getValueFromLogs({
            iface: offers.iface.bosonOfferHandlerIface,
            logs,
            eventArgsKey: "offerId",
            eventName: "OfferCreated"
        });
        return (offerId ||
            getValueFromLogs({
                iface: orchestration.iface.bosonOrchestrationHandlerIface,
                logs,
                eventArgsKey: "offerId",
                eventName: "OfferCreated"
            }))?.toString();
    }
    /**
     * Utility method to retrieve the created `offerIds` from logs after calling `createOfferBatch`
     * @param logs - Logs to search in.
     * @returns Array of created offerIds.
     */
    getCreatedOfferIdsFromLogs(logs) {
        return getValuesFromLogs({
            iface: offers.iface.bosonOfferHandlerIface,
            logs,
            eventArgsKey: "offerId",
            eventName: "OfferCreated"
        }).map((o) => o.toString());
    }
    /**
     * Utility method to retrieve the created `groupIds` from logs after calling `createGroup`
     * @param logs - Logs to search in.
     * @returns Array of group Ids.
     */
    getCreatedGroupIdsFromLogs(logs) {
        return getValuesFromLogs({
            iface: groups.iface.bosonGroupHandlerIface,
            logs,
            eventArgsKey: "groupId",
            eventName: "GroupCreated"
        }).map((g) => g.toString());
    }
    /**
     * Utility method to retrieve the created `sellerId` from logs after calling `createSeller`
     * or `createOfferAndSeller`.
     * @param logs - Logs to search in.
     * @returns Created offer id.
     */
    getCreatedSellerIdFromLogs(logs) {
        const sellerId = getValueFromLogs({
            iface: accounts.iface.bosonAccountHandlerIface,
            logs,
            eventArgsKey: "sellerId",
            eventName: "SellerCreated"
        });
        return (sellerId ||
            getValueFromLogs({
                iface: orchestration.iface.bosonOrchestrationHandlerIface,
                logs,
                eventArgsKey: "sellerId",
                eventName: "SellerCreated"
            }))?.toString();
    }
    /**
     * Utility method to retrieve the created `buyerId` from logs after calling `createBuyer`
     *
     * @param logs - Logs to search in.
     * @returns Created buyer id.
     */
    getCreatedBuyerIdFromLogs(logs) {
        const buyerId = getValueFromLogs({
            iface: accounts.iface.bosonAccountHandlerIface,
            logs,
            eventArgsKey: "buyerId",
            eventName: "BuyerCreated"
        });
        return (buyerId ||
            getValueFromLogs({
                iface: orchestration.iface.bosonOrchestrationHandlerIface,
                logs,
                eventArgsKey: "buyerId",
                eventName: "BuyerCreated"
            }))?.toString();
    }
    // Implementation
    async voidOffer(offerId, overrides = {}) {
        const { returnTxInfo } = overrides;
        const voidArgs = {
            offerId,
            web3Lib: this._web3Lib,
            subgraphUrl: this._subgraphUrl,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.voidOffer({
                ...voidArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.voidOffer({
                ...voidArgs,
                returnTxInfo: false
            });
        }
    }
    // Implementation
    async voidOfferBatch(offerIds, overrides = {}) {
        const { returnTxInfo } = overrides;
        const batchArgs = {
            offerIds,
            web3Lib: this._web3Lib,
            subgraphUrl: this._subgraphUrl,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.voidOfferBatch({
                ...batchArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.voidOfferBatch({
                ...batchArgs,
                returnTxInfo: false
            });
        }
    }
    // Implementation
    async voidNonListedOffer(fullOffer, overrides = {}) {
        const { returnTxInfo } = overrides;
        const voidArgs = {
            fullOffer,
            web3Lib: this._web3Lib,
            subgraphUrl: this._subgraphUrl,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.voidNonListedOffer({
                ...voidArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.voidNonListedOffer({
                ...voidArgs,
                returnTxInfo: false
            });
        }
    }
    // Implementation
    async voidNonListedOfferBatch(fullOffers, overrides = {}) {
        const { returnTxInfo } = overrides;
        const voidArgs = {
            fullOffers,
            web3Lib: this._web3Lib,
            subgraphUrl: this._subgraphUrl,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.voidNonListedOfferBatch({
                ...voidArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.voidNonListedOfferBatch({
                ...voidArgs,
                returnTxInfo: false
            });
        }
    }
    // Implementation
    async extendOffer(offerId, validUntil, overrides = {}) {
        const { returnTxInfo } = overrides;
        const extendArgs = {
            offerId,
            validUntil,
            web3Lib: this._web3Lib,
            subgraphUrl: this._subgraphUrl,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.extendOffer({
                ...extendArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.extendOffer({
                ...extendArgs,
                returnTxInfo: false
            });
        }
    }
    // Implementation
    async extendOfferBatch(offerIds, validUntil, overrides = {}) {
        const { returnTxInfo } = overrides;
        const batchArgs = {
            offerIds,
            validUntil,
            web3Lib: this._web3Lib,
            subgraphUrl: this._subgraphUrl,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.extendOfferBatch({
                ...batchArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.extendOfferBatch({
                ...batchArgs,
                returnTxInfo: false
            });
        }
    }
    /**
     * Returns offer from subgraph.
     * @param offerId - ID of offer.
     * @param queryVars - Optional query variables to skip, order or filter.
     * @returns Offer entity from subgraph.
     */
    async getOfferById(offerId, queryVars) {
        return offers.subgraph.getOfferById(this._subgraphUrl, offerId, queryVars);
    }
    /**
     * Returns offers from subgraph.
     * @param queryVars - Optional query variables to skip, order or filter.
     * @returns Offer entities from subgraph.
     */
    async getOffers(queryVars) {
        return offers.subgraph.getOffers(this._subgraphUrl, queryVars);
    }
    /**
     * Renders contractual agreement for given offer.
     * @param offerId - Id of offer to render agreement for.
     * @returns Contractual agreement as string.
     */
    async renderContractualAgreementForOffer(offerId) {
        const offerData = await offers.subgraph.getOfferById(this._subgraphUrl, offerId);
        return offers.renderContractualAgreementForOffer(offerData);
    }
    /**
     * Renders contractual agreement for given offer.
     * @param template - Mustache syntax based template.
     * @param offerData - Offer data.
     * @returns Contractual agreement as string.
     */
    async renderContractualAgreement(template, offerData, offerMetadata) {
        const tokenInfo = await this.getExchangeTokenInfo(offerData.exchangeToken);
        return offers.renderContractualAgreement(template, offerData, offerMetadata, tokenInfo);
    }
    // Implementation
    async reserveRange(offerId, length, to, overrides = {}) {
        const { returnTxInfo } = overrides;
        const offer = await this.getOfferById(offerId);
        const reserveArgs = {
            offerId,
            length,
            to: to === "contract"
                ? offer.seller.voucherCloneAddress
                : offer.seller.assistant,
            subgraphUrl: this._subgraphUrl,
            contractAddress: overrides.contractAddress || this._protocolDiamond,
            web3Lib: this._web3Lib
        };
        if (returnTxInfo === true) {
            return offers.handler.reserveRange({
                ...reserveArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.reserveRange({
                ...reserveArgs,
                returnTxInfo: false
            });
        }
    }
    /* -------------------------------------------------------------------------- */
    /*                   ERC20 / Exchange Token related methods                   */
    /* -------------------------------------------------------------------------- */
    /**
     * Returns the current allowance of the given token by calling the contract.
     * @param exchangeToken - Address of exchange token.
     * @param overrides - Optional overrides.
     * @returns Allowance for given signer.
     */
    async getExchangeTokenAllowance(exchangeToken, overrides = {}) {
        return erc20.handler.getAllowance({
            web3Lib: this._web3Lib,
            contractAddress: exchangeToken,
            spender: overrides.spender || this._protocolDiamond,
            owner: overrides.owner || (await this._web3Lib.getSignerAddress())
        });
    }
    /**
     * Returns `name`, `decimals` and `symbol` of the given token by calling the contract.
     * @param exchangeToken - Address exchange token.
     * @returns Decimals, name and symbol.
     */
    async getExchangeTokenInfo(exchangeToken) {
        if (this._tokenInfoManager === undefined) {
            this._tokenInfoManager = new TokenInfoManager(this._chainId, this._web3Lib, this._subgraphUrl);
        }
        return this._tokenInfoManager.getExchangeTokenInfo(exchangeToken);
    }
    // Implementation
    async approveExchangeToken(exchangeToken, value, overrides = {}) {
        const { returnTxInfo } = overrides;
        const approveArgs = {
            contractAddress: exchangeToken,
            spender: overrides.spender || this._protocolDiamond,
            value,
            web3Lib: this._web3Lib
        };
        if (returnTxInfo === true) {
            return erc20.handler.approve({
                ...approveArgs,
                returnTxInfo: true
            });
        }
        else {
            return erc20.handler.approve({
                ...approveArgs,
                returnTxInfo: false
            });
        }
    }
    async getProtocolAllowance(exchangeToken, overrides = {}) {
        return erc20.handler.getAllowance({
            contractAddress: exchangeToken,
            owner: overrides.owner || (await this._web3Lib.getSignerAddress()),
            spender: overrides.spender || this._protocolDiamond,
            web3Lib: this._web3Lib
        });
    }
    async checkTokenGatedCondition(offerId, buyerAddress) {
        const offer = await this.getOfferById(offerId);
        if (!offer?.condition) {
            return true;
        }
        const offerConditionId = offer.condition.id;
        const getCanTokenIdBeUsedToCommit = async () => {
            const conditionalCommitLogs = await this.getConditionalCommitAuthorizedEventLogs({
                conditionalCommitAuthorizedLogsFilter: {
                    groupId: offerConditionId, // all offers of the same product have the same condition.id
                    buyerAddress
                }
            });
            const tokenIdMapWithMultipleEntries = groupBy(conditionalCommitLogs, (log) => log.tokenId);
            // build a map of tokenId -> log with the min available commits (or the most recent log) in all the logs
            const tokenIdToAvailableCommitsMap = new Map();
            Object.entries(tokenIdMapWithMultipleEntries).forEach(([tokenId, logs]) => {
                let logWithMinAvailableCommits = logs[0];
                for (const log of logs) {
                    const currentAvailableCommits = Number(log.maxCommits) - Number(log.commitCount);
                    const savedAvailableCommits = Number(logWithMinAvailableCommits.maxCommits) -
                        Number(logWithMinAvailableCommits.commitCount);
                    if (currentAvailableCommits < savedAvailableCommits) {
                        logWithMinAvailableCommits = log;
                    }
                }
                tokenIdToAvailableCommitsMap.set(tokenId, logWithMinAvailableCommits);
            });
            const canTokenIdBeUsedToCommit = (tokenId) => {
                if (!tokenIdToAvailableCommitsMap.has(tokenId)) {
                    return true;
                }
                const log = tokenIdToAvailableCommitsMap.get(tokenId);
                if (!log) {
                    return true;
                }
                return Number(log.maxCommits) - Number(log.commitCount) > 0;
            };
            return canTokenIdBeUsedToCommit;
        };
        const getCurrentCommits = async () => {
            const buyers = await this.getBuyers({
                buyersFilter: {
                    wallet: buyerAddress
                },
                includeExchanges: true,
                exchangesFilter: {
                    offer_: {
                        condition: offerConditionId
                    }
                }
            });
            const [buyer] = buyers ?? [];
            return buyer?.exchanges?.length ?? 0;
        };
        const concurrencyLimit = 5;
        const { minTokenId: _minTokenId, maxTokenId: _maxTokenId, tokenType, threshold, method, tokenAddress, gatingType, maxCommits: _maxCommits } = offer.condition;
        const minTokenId = BigInt(_minTokenId);
        const maxTokenId = BigInt(_maxTokenId);
        const returnTrueBecauseItCannotBeChecked = () => {
            const maxRequestsThreshold = 10;
            if (minTokenId > maxTokenId) {
                return true; // leave the smart contract check this
            }
            if (maxTokenId - minTokenId > maxRequestsThreshold) {
                return true; // leave the smart contract check this
            }
        };
        const maxCommits = Number(_maxCommits);
        if (tokenType === TokenType.FungibleToken) {
            const balance = await erc20.handler.balanceOf({
                contractAddress: tokenAddress,
                owner: buyerAddress,
                web3Lib: this._web3Lib
            });
            if (!BigNumber.from(balance).gte(threshold)) {
                return false;
            }
            const currentCommits = await getCurrentCommits();
            return currentCommits < maxCommits;
        }
        if (tokenType === TokenType.NonFungibleToken) {
            if (gatingType === GatingType.PerAddress) {
                if (method === EvaluationMethod.Threshold) {
                    const balance = await erc721.handler.balanceOf({
                        contractAddress: tokenAddress,
                        owner: buyerAddress,
                        web3Lib: this._web3Lib
                    });
                    if (!BigNumber.from(balance).gte(threshold)) {
                        return false;
                    }
                    const currentCommits = await getCurrentCommits();
                    return currentCommits < maxCommits;
                }
                if (method === EvaluationMethod.TokenRange) {
                    if (returnTrueBecauseItCannotBeChecked()) {
                        return true;
                    }
                    const promises = [];
                    for (let i = minTokenId; i <= maxTokenId; i++) {
                        const tokenId = i;
                        promises.push(() => erc721.handler.ownerOf({
                            contractAddress: tokenAddress,
                            tokenId,
                            web3Lib: this._web3Lib
                        }));
                    }
                    const currentCommits = await getCurrentCommits();
                    for await (const owners of batchTasks(promises, concurrencyLimit)) {
                        if (owners.some((owner) => owner === buyerAddress)) {
                            return currentCommits < maxCommits;
                        }
                    }
                    return false;
                }
                throw new Error(`Unsupported method=${method} for this tokenType=${tokenType} and gatingType=${gatingType}`);
            }
            if (gatingType === GatingType.PerTokenId) {
                if (method === EvaluationMethod.TokenRange) {
                    if (returnTrueBecauseItCannotBeChecked()) {
                        return true;
                    }
                    const canTokenIdBeUsedToCommit = await getCanTokenIdBeUsedToCommit();
                    const promises = [];
                    for (let i = minTokenId; i <= maxTokenId; i++) {
                        const tokenId = i;
                        promises.push(() => erc721.handler.ownerOf({
                            contractAddress: tokenAddress,
                            tokenId,
                            web3Lib: this._web3Lib
                        }));
                    }
                    let tokenId = minTokenId;
                    for await (const owners of batchTasks(promises, concurrencyLimit)) {
                        if (owners.some((owner) => owner === buyerAddress) &&
                            canTokenIdBeUsedToCommit(tokenId.toString())) {
                            return true;
                        }
                        tokenId++;
                    }
                    return false;
                }
                throw new Error(`Unsupported method=${method} for this tokenType=${tokenType} and gatingType=${gatingType}`);
            }
            throw new Error(`Unsupported gatingType=${gatingType} for this tokenType=${tokenType}`);
        }
        if (tokenType === TokenType.MultiToken) {
            if (gatingType === GatingType.PerAddress) {
                if (returnTrueBecauseItCannotBeChecked()) {
                    return true;
                }
                const promises = [];
                for (let i = minTokenId; i <= maxTokenId; i++) {
                    const tokenId = i;
                    promises.push(() => erc1155.handler.balanceOf({
                        contractAddress: tokenAddress,
                        tokenId,
                        owner: buyerAddress,
                        web3Lib: this._web3Lib
                    }));
                }
                for await (const balances of batchTasks(promises, concurrencyLimit)) {
                    if (balances.some((balance) => BigNumber.from(balance).gte(threshold))) {
                        return true;
                    }
                }
                return false;
            }
            if (gatingType === GatingType.PerTokenId) {
                if (returnTrueBecauseItCannotBeChecked()) {
                    return true;
                }
                const canTokenIdBeUsedToCommit = await getCanTokenIdBeUsedToCommit();
                const promises = [];
                for (let i = minTokenId; i <= maxTokenId; i++) {
                    const tokenId = i;
                    promises.push(() => erc1155.handler.balanceOf({
                        contractAddress: tokenAddress,
                        tokenId,
                        owner: buyerAddress,
                        web3Lib: this._web3Lib
                    }));
                }
                let tokenId = minTokenId;
                for await (const balances of batchTasks(promises, concurrencyLimit)) {
                    if (balances.some((balance) => BigNumber.from(balance).gte(threshold)) &&
                        canTokenIdBeUsedToCommit(tokenId.toString())) {
                        return true;
                    }
                    tokenId++;
                }
                return false;
            }
            throw new Error(`Unsupported gatingType=${gatingType} for this tokenType=${tokenType}`);
        }
        throw new Error(`Unsupported tokenType=${tokenType}`);
    }
    /**
     * Check a given offer meets ExchangePolicy rules.
     * @param offerId - Id of offer to render agreement for.
     * @returns Contractual agreement as string.
     */
    async checkExchangePolicy(offerId, rules) {
        const offerData = await offers.subgraph.getOfferById(this._subgraphUrl, offerId);
        return offers.checkExchangePolicy(offerData, rules);
    }
    // Implementation
    async updateOfferRoyaltyRecipients(offerId, royaltyInfo, overrides = {}) {
        const { returnTxInfo } = overrides;
        const royaltyArgs = {
            offerId,
            royaltyInfo,
            web3Lib: this._web3Lib,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.updateOfferRoyaltyRecipients({
                ...royaltyArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.updateOfferRoyaltyRecipients({
                ...royaltyArgs,
                returnTxInfo: false
            });
        }
    }
    // Implementation
    async updateOfferRoyaltyRecipientsBatch(offerIds, royaltyInfo, overrides = {}) {
        const { returnTxInfo } = overrides;
        const batchArgs = {
            offerIds,
            royaltyInfo,
            web3Lib: this._web3Lib,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        };
        if (returnTxInfo === true) {
            return offers.handler.updateOfferRoyaltyRecipientsBatch({
                ...batchArgs,
                returnTxInfo: true
            });
        }
        else {
            return offers.handler.updateOfferRoyaltyRecipientsBatch({
                ...batchArgs,
                returnTxInfo: false
            });
        }
    }
    async getOfferHash(fullOfferArgsUnsigned, overrides = {}) {
        return offers.handler.getOfferHash({
            fullOfferArgsUnsigned,
            web3Lib: this._web3Lib,
            contractAddress: overrides.contractAddress || this._protocolDiamond
        });
    }
}
//# sourceMappingURL=mixin.js.map