import * as yup from "yup";
import { BigNumber } from "@ethersproject/bignumber";
import { PriceType, utils } from "@bosonprotocol/common";
import Mustache from "mustache";
import { formatUnits } from "@ethersproject/units";
import { MetadataType, ItemMetadataType } from "../subgraph";
import { AddressZero } from "@ethersproject/constants";
export class InvalidOfferDataError extends Error {
    missingProperties;
    constructor(missingProperties) {
        super(`InvalidOfferData - missing properties: [${missingProperties.join(",")}]`);
        this.missingProperties = missingProperties;
    }
}
export class InvalidOfferMetadataError extends Error {
    missingProperties;
    constructor(missingProperties) {
        super(`InvalidOfferMetadata - missing properties: [${missingProperties.join(",")}]`);
        this.missingProperties = missingProperties;
    }
}
export const baseOfferDataSchema = yup
    .object({
    price: yup.mixed().required(),
    sellerDeposit: yup.mixed().required(),
    agentId: yup.mixed().required(),
    buyerCancelPenalty: yup.mixed().required(),
    quantityAvailable: yup.mixed().required(),
    validFromDateInMS: yup.mixed().required(),
    validUntilDateInMS: yup.mixed().required(),
    voucherRedeemableFromDateInMS: yup.mixed().required(),
    voucherRedeemableUntilDateInMS: yup.mixed().required(),
    disputePeriodDurationInMS: yup.mixed().required(),
    resolutionPeriodDurationInMS: yup.mixed().required(),
    exchangeToken: yup.string().required(),
    disputeResolverId: yup.mixed().required(),
    metadataUri: yup.string().required(),
    metadataHash: yup.string().required()
})
    .required();
export const baseOfferMetadataSchema = yup
    .object({
    sellerContactMethod: yup.string().required(),
    disputeResolverContactMethod: yup.string().required(),
    escalationDeposit: yup.mixed().required(),
    escalationResponsePeriodInSec: yup.mixed().required(),
    sellerTradingName: yup.string().required(),
    returnPeriodInDays: yup.number().required()
})
    .required();
function checkOfferDataIsValid(offerData, throwIfInvalid = false) {
    if (offerData === undefined || offerData === null) {
        throw new Error("InvalidOfferData - undefined");
    }
    if (typeof offerData !== "object") {
        throw new Error("InvalidOfferData - expecting an object");
    }
    try {
        baseOfferDataSchema.validateSync(offerData, { abortEarly: false });
    }
    catch (e) {
        const missingProperties = [];
        const getMissingProp = (error) => {
            return error.match(/(.*) is a required field/)[1] || error;
        };
        if (throwIfInvalid) {
            if (e.errors) {
                e.errors.forEach((error) => {
                    missingProperties.push(getMissingProp(error));
                });
            }
            else {
                missingProperties.push(getMissingProp(e));
            }
            throw new InvalidOfferDataError(missingProperties);
        }
        return false;
    }
    return true;
}
function checkOfferMetadataIsValid(offerMetadata, throwIfInvalid = false) {
    if (offerMetadata === undefined || offerMetadata === null) {
        throw new Error("InvalidOfferMetadata - undefined");
    }
    if (typeof offerMetadata !== "object") {
        throw new Error("InvalidOfferMetadata - expecting an object");
    }
    try {
        baseOfferMetadataSchema.validateSync(offerMetadata, { abortEarly: false });
    }
    catch (e) {
        const missingProperties = [];
        const getMissingProp = (error) => {
            return error.match(/(.*) is a required field/)[1] || error;
        };
        if (throwIfInvalid) {
            if (e.errors) {
                e.errors.forEach((error) => {
                    missingProperties.push(getMissingProp(error));
                });
            }
            else {
                missingProperties.push(getMissingProp(e));
            }
            throw new InvalidOfferMetadataError(missingProperties);
        }
        return false;
    }
    return true;
}
function convertExistingOfferData(offerDataSubGraph) {
    const productItemMetadata = offerDataSubGraph.metadata?.type === MetadataType.BUNDLE
        ? offerDataSubGraph.metadata.items?.find((item) => item.type === ItemMetadataType.ITEM_PRODUCT_V1)
        : undefined;
    return {
        offerData: {
            ...offerDataSubGraph,
            collectionIndex: offerDataSubGraph.collectionIndex || 0,
            validFromDateInMS: offerDataSubGraph.validFromDate,
            validUntilDateInMS: offerDataSubGraph.validUntilDate,
            voucherRedeemableFromDateInMS: offerDataSubGraph.voucherRedeemableFromDate,
            voucherRedeemableUntilDateInMS: offerDataSubGraph.voucherRedeemableUntilDate,
            voucherValidDurationInMS: offerDataSubGraph.voucherValidDuration,
            disputePeriodDurationInMS: offerDataSubGraph.disputePeriodDuration,
            resolutionPeriodDurationInMS: offerDataSubGraph.resolutionPeriodDuration,
            exchangeToken: offerDataSubGraph.exchangeToken.address,
            feeLimit: offerDataSubGraph.price, // feeLimit is never stored on-chain. By default, set it to offer price
            priceType: PriceType.Static,
            royaltyInfo: {
                recipients: [AddressZero], // AddressZero means Seller's treasury account
                bps: ["0"] // Values should be greater or equal than Seller's minimum royalty amount
            }
        },
        offerMetadata: {
            sellerContactMethod: offerDataSubGraph.metadata?.exchangePolicy.sellerContactMethod,
            disputeResolverContactMethod: (productItemMetadata ||
                offerDataSubGraph.metadata)?.exchangePolicy.disputeResolverContactMethod,
            escalationDeposit: offerDataSubGraph.disputeResolutionTerms.buyerEscalationDeposit,
            escalationResponsePeriodInSec: offerDataSubGraph.disputeResolutionTerms.escalationResponsePeriod,
            sellerTradingName: (productItemMetadata ||
                offerDataSubGraph.metadata)?.productV1Seller?.name,
            returnPeriodInDays: (productItemMetadata ||
                offerDataSubGraph.metadata)?.shipping.returnPeriodInDays
        },
        tokenInfo: {
            ...offerDataSubGraph.exchangeToken,
            decimals: parseInt(offerDataSubGraph.exchangeToken.decimals)
        }
    };
}
const MAX_AND_MIN_DATE_TIMESTAMP = 8.64e15; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
export async function prepareRenderingData(offerData, offerMetadata, tokenInfo) {
    return {
        ...offerData,
        ...offerMetadata,
        priceValue: formatUnits(offerData.price, tokenInfo.decimals),
        exchangeTokenSymbol: tokenInfo.symbol,
        sellerDepositValue: formatUnits(offerData.sellerDeposit, tokenInfo.decimals),
        buyerCancelPenaltyValue: formatUnits(offerData.buyerCancelPenalty, tokenInfo.decimals),
        escalationDepositValue: formatUnits(offerMetadata.escalationDeposit, tokenInfo.decimals),
        hasExpirationDate: !!offerData.validUntilDateInMS &&
            Number(offerData.validUntilDateInMS.toString()) <
                MAX_AND_MIN_DATE_TIMESTAMP,
        toISOString: function () {
            return function (num, render) {
                try {
                    return new Date(parseInt(render(num))).toISOString();
                }
                catch {
                    return `[[${num} - invalid Date]]`;
                }
            };
        },
        msecToDay: function () {
            return function (num, render) {
                try {
                    return BigNumber.from(render(num))
                        .div(utils.timestamp.MSEC_PER_DAY)
                        .toString();
                }
                catch {
                    return `[[${num} - invalid BigNumber]]`;
                }
            };
        },
        secToDay: function () {
            return function (num, render) {
                try {
                    return BigNumber.from(render(num))
                        .div(utils.timestamp.SEC_PER_DAY)
                        .toString();
                }
                catch {
                    return `[[${num} - invalid BigNumber]]`;
                }
            };
        }
    };
}
// inject a template + all offer details
export async function renderContractualAgreement(template, offerData, offerMetadata, tokenInfo) {
    // Check the passed offerData is matching the required type
    checkOfferDataIsValid(offerData, true);
    checkOfferMetadataIsValid(offerMetadata, true);
    const preparedData = await prepareRenderingData(offerData, offerMetadata, tokenInfo);
    return Mustache.render(template, preparedData);
}
export async function renderContractualAgreementForOffer(existingOfferData) {
    if (!existingOfferData) {
        throw new Error(`offerData is undefined`);
    }
    if (!existingOfferData.metadata) {
        throw new Error(`Offer Metadata is undefined`);
    }
    if (existingOfferData.metadata.type !== MetadataType.PRODUCT_V1) {
        throw new Error(`Invalid Offer Metadata: Type is not supported: '${existingOfferData.metadata.type}'`);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    if (!existingOfferData.metadata.exchangePolicy) {
        throw new Error(`Invalid Offer Metadata: exchangePolicy is not defined`);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    if (!existingOfferData.metadata.exchangePolicy.template) {
        throw new Error(`Invalid Offer Metadata: exchangePolicy.template is not defined`);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const template = existingOfferData.metadata.exchangePolicy.template;
    const convertedOfferArgs = convertExistingOfferData(existingOfferData);
    return renderContractualAgreement(template, convertedOfferArgs.offerData, convertedOfferArgs.offerMetadata, convertedOfferArgs.tokenInfo);
}
//# sourceMappingURL=renderContractualAgreement.js.map