import {
    CancelCheckoutProperties,
    CartProperties,
    CheckoutProperties,
    DeliveryProperties,
    DrugProperties,
    ECommerceTypes,
    FacilityProperties,
    GroceryProperties,
    ItemAction,
    ItemObject,
    ItemProperties,
    ItemReportProperties,
    ItemRequestProperties,
    ItemType,
    ItemVerificationProperties,
    ShopMode
} from "./typings"

import {BloodProperties} from "./blood.typings"

import ECommmercePropertiesTI from './typings-ti'
import BloodPropertiesTI from './blood.typings-ti'
import OxygenPropertiesTI from './oxygen.typings-ti'

import CommonTypesTI from '../../core/commonTypes-ti'

import {injectEvent} from "../../core/injector"

import CfCore from "../../core/CfCore"
import {ContentBlock, NavigationTypes} from "../Navigation/typings"
import {ICatalogRepository} from "../../core/repositories/catalog/CatalogRepository"
import {createCheckers} from "ts-interface-checker"
import {hasRepeatedIds, isISOLocal, toISOLocal} from "../../utils"
import {OxygenProperties} from "./oxygen.typings"
import {MedicalEquipmentProperties} from "./medicalEquipment.typings"
import CfExceptionHandler from "../../core/CfExceptionHandler";

const moduleName = ContentBlock.ECommerce


let catalogRepository: ICatalogRepository;

/**
 * This function must be called when the user interrupts the ordering,
 * at any moment
 *
 * @param properties
 * @param sendNow
 */
const logCancelCheckoutEvent = (properties: CancelCheckoutProperties, sendNow = false) => {

    if (!properties.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.CancelCheckout, 'empty cancel checkout id')
    } else if (properties.items.length == 0) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.CancelCheckout, 'cancel checkout empty items list')
    } else if (hasRepeatedIds(properties.items)) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.CancelCheckout, 'cancel checkout items contains repeated ids')
    }

    injectEvent(
        properties,
        [ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI],
        ECommerceTypes.CancelCheckout,
        moduleName,
        '',
        sendNow
    )
}


/**
 * This function must be called when the user checkout the current
 * cart contents
 *
 * @param {CheckoutProperties} properties
 * @param userId use this parameter when the logger user is not the same than the user who has created this log
 * @param {boolean} sendNow When true, try to send the event immediately
 * without enqueueing it
 *
 * @throws Will throw the error 'currency-missmatch'
 *         when the currency of some item is different
 *         than the currency of the whole cart
 *
 * @throws Will throw the error 'cfLog-instance-not-created'
 *         when {CfLog} have not been initialized
 */
const logCheckoutEvent = async (properties: CheckoutProperties, userId = '', sendNow = false): Promise<void> => {

    let shopMode = ShopMode.Delivery

    properties.items.every(itemObject => {
        verifyItemObject(ECommerceTypes.Checkout, itemObject)
    })
    const abnormalCurrency = properties.items.find(item => item.currency !== properties.currency)
    if (!properties.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Checkout, 'empty checkout id')
    } else if (!properties.cart_id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Checkout, 'empty checkout cart id')
    } else if (properties.cart_price < 0) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Checkout, 'negative checkout price')
    } else if (properties.items.length == 0) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Checkout, 'checkout empty items list')
    } else if (hasRepeatedIds(properties.items)) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Checkout, 'checkout items list contains repeated ids')
    } else if (!properties.currency) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Checkout, 'checkout currency required')
    } else if (abnormalCurrency) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Checkout, 'checkout currency mismatch')
    } else if (properties.is_successful == undefined) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Checkout, 'checkout is_successful required')
    } else if (properties.shop_mode) {
        shopMode = properties.shop_mode
    }

    const internalCheckoutProperties = {
        ...properties,
        shop_mode: shopMode,
    }

    injectEvent(
        internalCheckoutProperties,
        [ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI],
        ECommerceTypes.Checkout,
        moduleName,
        userId,
        sendNow)
}


/**
 * Whenever the user adds or removes an item from the card,
 * this function must be called to log that event
 *
 * @param {CartProperties} properties Object with the information
 * to define both, the action to perform in the Cart, and the
 * item
 * @param {boolean} sendNow When true, try to send the event immediately
 * without enqueueing it
 *
 * @throws Will throw the error 'currency-missmatch'
 *         when the currency of the given item is different from the currency of the whole cart
 *
 * @throws Will throw the error 'cfLog-instance-not-created'
 *         when {CfLog} have not been initialized
 */
const logCartEvent = async (properties: CartProperties, sendNow = false): Promise<void> => {

    if (properties) {
        verifyItemObject(ECommerceTypes.Cart, properties.item)
    } else {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Cart, 'invalid cart item object')
    }
    if (!properties.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Cart, 'empty cart id')
    } else if (!properties.action) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Cart, 'cart action required')
    } else if (properties.cart_price < 0) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Cart, 'negative cart price')
    } else if (!properties.currency) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Cart, 'cart currency required')
    } else if (!properties.item) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Cart, 'cart item object required')
    } else if (properties.currency !== properties.item.currency) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Cart, 'different currency codes provided')
    }

    const internalCartProperties = {
        ...properties,
    }

    injectEvent(
        internalCartProperties,
        [ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI],
        ECommerceTypes.Cart,
        moduleName,
        '',
        sendNow)
}


/**
 * Use this method when the items have been already delivered
 *
 *
 * @param {DeliveryProperties} properties
 * @param {string} userId use this parameter when the logger user is not the same than the user who has created this log
 * @param {boolean} sendNow When true, try to send the event immediately
 * without enqueueing it
 *
 */
const logDeliveryEvent = (properties: DeliveryProperties, userId = '', sendNow = false): void => {

    if (!properties.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Delivery, 'empty delivery id')
    } else if (!properties.action) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Delivery, 'delivery action required')
    } else if (!properties.order_id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Delivery, 'empty delivery order id')
    } else if (!properties.est_delivery_ts) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Delivery, 'delivery time required')
    } else if (!properties.is_urgent) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Delivery, 'delivery is_urgent value required')
    } else if (properties.delivery_coordinates && (properties.delivery_coordinates?.lat == 0 ||
        properties.delivery_coordinates?.lon == 0)) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Delivery, 'Invalid delivery_coordinates provided')
    } else if (properties.dispatch_coordinates && (properties.dispatch_coordinates?.lat == 0 ||
        properties.dispatch_coordinates?.lon == 0)) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Delivery, 'Invalid dispatch_coordinates provided')
    }

    if(properties.est_delivery_ts instanceof Date){
        properties.est_delivery_ts = toISOLocal(properties.est_delivery_ts)
    }else if(!isISOLocal(properties.est_delivery_ts)) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.Delivery, 'invalid delivery date format')
    }


    injectEvent(
        properties,
        [ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI],
        ECommerceTypes.Delivery,
        moduleName,
        userId,
        sendNow)
}

/**
 * Event to trigger when there is a report about the item submitted.
 *
 * @param properties properties for the item report event.
 * @param sendNow When true, try to send the event immediately without enqueueing it
 */
const logItemReportEvent = (properties: ItemReportProperties, sendNow = false): void => {

    if (!properties.item.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemReport, 'empty item report id')
    } else if (!properties.store_info) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemReport, 'report store object required')
    } else if (!properties.store_info.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemReport, 'empty report store id')
    } else if (!properties.report_info) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemReport, 'empty report info object')
    } else if (!properties.report_info.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemReport, 'empty report id')
    } else if (!properties.report_info.short_desc) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemReport, 'empty report short desc')
    }

    injectEvent(
        properties,
        [ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI],
        ECommerceTypes.ItemReport,
        moduleName,
        '',
        sendNow)
}

/**
 * Event to trigger when there is a request about the item submitted.
 *
 * @param properties properties for the item request event.
 * @param sendNow When true, try to send the event immediately without enqueueing it
 */
const logItemRequestEvent = (properties: ItemRequestProperties, sendNow = false): void => {

    if (!properties.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemRequest, 'empty request id')
    } else if (!properties.item_name) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemRequest, 'empty request item name')
    } else if (!properties.manufacturer) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemRequest, 'empty request item manufacturer')
    }

    injectEvent(
        properties,
        [ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI],
        ECommerceTypes.ItemRequest,
        moduleName,
        '',
        sendNow)
}

/**
 * Event to trigger when there is a verification request about the item.
 *
 * @param properties properties for the item request event.
 * @param sendNow When true, try to send the event immediately without enqueueing it
 */
const logItemVerificationEvent = (properties: ItemVerificationProperties, sendNow = false): void => {

    if (!properties.scan_channel) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemVerification, 'invalid verification scan channel')
    } else if (!properties.scan_type) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemVerification, 'invalid verification scan type')
    } else if (properties.is_successful == undefined) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemVerification, 'invalid verification is_successful')
    } else if (!properties.item_info.id) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemVerification, 'empty verification item id')
    } else if (!properties.item_info.type) {
        new CfExceptionHandler().throwAndLogError(ECommerceTypes.ItemVerification, 'invalid verification item type')
    }

    injectEvent(
        properties,
        [ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI],
        ECommerceTypes.ItemVerification,
        moduleName,
        '',
        sendNow)
}


/**
 * Use this function to log whenever the user interacts with an item
 *
 * @param { ItemProperties } properties
 * @param { DrugProperties } drugProperties Information related to drug features
 * to be injected in the server catalog. Only needed when ItemProperties.action is
 * `ItemAction.view`
 * @param { boolean } sendNow When true, try to send the event immediately
 * without enqueueing it
 *
 * @throws Will throw the error 'cfLog-instance-not-created'
 *         when {CfLog} have not been initialized
 *
 * @throws Will throw the error 'currency-missmatch'
 *         when the currency of the given item is different
 *         than the currency of the whole cart
 */
const logItemEvent = async (
    properties: ItemProperties,
    productProperties?: BloodProperties | DrugProperties | OxygenProperties | MedicalEquipmentProperties | GroceryProperties,
    sendNow = false
): Promise<void> => {

    const {ItemProperties: PropertiesChecker} = createCheckers(
        ECommmercePropertiesTI,
        BloodPropertiesTI,
        OxygenPropertiesTI,
        CommonTypesTI)

    PropertiesChecker.check(properties)

    verifyItemObject(ECommerceTypes.Item, properties.item)

    const internalItemProperties = {
        ...properties,
    }

    injectEvent(
        internalItemProperties,
        [ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI],
        ECommerceTypes.Item,
        moduleName,
        '',
        sendNow)

    if ((properties.action === ItemAction.View || properties.action === ItemAction.Impression) && productProperties) {

        if (properties.item.type === ItemType.Drug) {
            const drugProperties = productProperties as DrugProperties

            if (!drugProperties.name) {
                new CfExceptionHandler().throwAndLogError("drug catalog", 'drug name is required.')
            }

            catalogRepository.injectDrug(
                properties.item.id,
                drugProperties)
        } else if (properties.item.type === ItemType.Grocery) {
            const groceryProperties = productProperties as GroceryProperties

            if (!groceryProperties.name) {
                new CfExceptionHandler().throwAndLogError("grocery catalog", 'grocery name is required.')
            }

            catalogRepository.injectGrocery(
                properties.item.id,
                groceryProperties)
        } else if (properties.item.type === ItemType.Blood) {
            const bloodProperties = productProperties as BloodProperties

            if (!bloodProperties.blood_group) {
                new CfExceptionHandler().throwAndLogError("blood catalog", 'blood group is required.')
            }

            catalogRepository.injectBlood(
                properties.item.id,
                bloodProperties)
        } else if (properties.item.type === ItemType.Oxygen) {
            const oxygenProperties = productProperties as OxygenProperties
            catalogRepository.injectOxygen(
                properties.item.id,
                oxygenProperties)
        } else if (properties.item.type === ItemType.MedicalEquipment) {
            const medicalEquipmentProperties = productProperties as MedicalEquipmentProperties

            if (!medicalEquipmentProperties.name) {
                new CfExceptionHandler().throwAndLogError("medical equipment catalog", 'medical equipment name is required.')
            }

            catalogRepository.injectMedicalEquipment(
                properties.item.id,
                medicalEquipmentProperties)
        }

    }

}

const updateGroceryCatalog = (itemId: string, properties: GroceryProperties): void => {

    if (!itemId) {
        new CfExceptionHandler().throwAndLogError("grocery catalog", 'empty grocery item id')
    } else if (!properties.name) {
        new CfExceptionHandler().throwAndLogError("grocery catalog", 'grocery name is required.')
    }
    catalogRepository.injectGrocery(itemId, properties)
}

const updateFacilityCatalog = (facilityId: string, properties: FacilityProperties): void => {

    if (!facilityId) {
        new CfExceptionHandler().throwAndLogError("facility catalog", 'empty facility item id')
    } else if (!properties.name) {
        new CfExceptionHandler().throwAndLogError("facility catalog", 'facility name is required.')
    }
    catalogRepository.injectFacility(facilityId, properties)
}

const updateDrugCatalog = (itemId: string, properties: DrugProperties): void => {

    if (!itemId) {
        new CfExceptionHandler().throwAndLogError("drug catalog", 'empty drug item id')
    } else if (!properties.name) {
        new CfExceptionHandler().throwAndLogError("drug catalog", 'drug name is required.')
    }

    catalogRepository.injectDrug(itemId, properties)
}

const updateBloodCatalog = (itemId: string, properties: BloodProperties): void => {

    if (!itemId) {
        new CfExceptionHandler().throwAndLogError("blood catalog", 'empty blood item id')
    } else if (!properties.blood_group) {
        new CfExceptionHandler().throwAndLogError("blood catalog", 'blood_group is required.')
    }
    catalogRepository.injectBlood(itemId, properties)
}

const updateOxygenCatalog = (itemId: string, properties: OxygenProperties): void => {

    if (!itemId) {
        new CfExceptionHandler().throwAndLogError("oxygen catalog", 'empty oxygen item id')
    }
    catalogRepository.injectOxygen(itemId, properties)
}

const updateMedicalEquipmentCatalog = (itemId: string, properties: MedicalEquipmentProperties): void => {

    if (!itemId) {
        new CfExceptionHandler().throwAndLogError("medical equipment catalog", 'empty medical equipment item id')
    } else if (!properties.name) {
        new CfExceptionHandler().throwAndLogError("medical equipment catalog", 'medical equipment name is required.')
    }
    catalogRepository.injectMedicalEquipment(itemId, properties)
}

/**
 * Start tracking impressions automatically for the items whose parent
 * is the element defined by the class `containerClassname`. A impression
 * is a visualization of an item for specific amount of time (i.e.: 1s)
 *
 * In addition, the items to track must include some metadata by using
 * the internal dataset of HTML elements, as shown in the next example.
 *
 * ```html
 *   <div
 *      data-log-id="0"
 *      data-log-quantity="9"
 *      data-log-price="426"
 *      data-log-currency="EUR"
 *      data-log-stock-status="in_stock"
 *      data-log-promo-id="your-promo-id"
 *      class="item-card"
 *   >
 * ```
 *
 * @param {string} containerClassname Represents the CSS classname of the
 * container to monitorize
 *
 * @param {string} itemClassname Any item within the indicated container must
 * also include a common classname to let the SDK monitorize it
 *
 * @throws Will throw the error 'container-does-not-exist'
 *         when `containerClassname` does not correspond to
 *         any DOM element
 */
const startTrackingImpressions = (containerClassname: string, itemClassname: string, searchId: string) => {

    const impressionHandler = ({dataset, appData}) => {
        const {
            logId: id,
            logCurrency: currency,
            logType: type,
            logPrice: price,
            logQuantity: quantity,
            logStockStatus: stock_status,
            logPromoId: promo_id,
            logFacilityId: facility_id,
            logDrugCatalogObject: drug_catalog_object
        } = dataset

        const itemDetail: ItemObject = {
            id,
            type: type || ItemType.Drug,
            currency: currency,
            price: parseInt(price),
            quantity: parseInt(quantity),
            stock_status: stock_status,
            promo_id: promo_id || "",
            facility_id: facility_id || "",
        }

        let catalogObj;
        let catalogData;

        if (drug_catalog_object) {
            try {
                catalogObj = JSON.parse(drug_catalog_object)
                if (type === ItemType.Drug) {
                    catalogData = {
                        market_id: catalogObj.market_id,
                        name: catalogObj.name,
                        description: catalogObj.description,
                        supplier_id: catalogObj.supplier_id,
                        supplier_name: catalogObj.supplier_name,
                        producer: catalogObj.producer || "",
                        packaging: catalogObj.packaging || "",
                        active_ingredients: catalogObj.active_ingredients,
                        drug_form: catalogObj.drug_form || "",
                        drug_strength: catalogObj.drug_strength || "",
                        atc_anatomical_group: catalogObj.atc_anatomical_group || "",
                        otc_or_ethical: catalogObj.otc_or_ethical || ""
                    }
                } else if (type === ItemType.Blood) {
                    catalogData = {
                        market_id: catalogObj.market_id,
                        blood_component: catalogObj.blood_component,
                        blood_group: catalogObj.blood_group,
                        packaging: catalogObj.packaging,
                        packaging_size: catalogObj.packaging_size,
                        supplier_id: catalogObj.supplier_id || "",
                        supplier_name: catalogObj.supplier_name || ""
                    }
                } else if (type === ItemType.MedicalEquipment) {
                    catalogData = {
                        name: catalogObj.name,
                        market_id: catalogObj.market_id,
                        description: catalogObj.description || "",
                        supplier_id: catalogObj.supplier_id,
                        supplier_name: catalogObj.supplier_name,
                        producer: catalogObj.producer || "",
                        packaging: catalogObj.packaging || "",
                        category: catalogObj.category || ""
                    }
                } else if (type === ItemType.Oxygen) {
                    catalogData = {
                        market_id: catalogObj.market_id,
                        packaging: catalogObj.packaging,
                        packaging_size: catalogObj.packaging_size,
                        supplier_id: catalogObj.supplier_id || "",
                        supplier_name: catalogObj.supplier_name || ""
                    }
                }
            } catch (e) {
                console.log('Malformed catalog object')
            }
        }


        verifyItemObject(ECommerceTypes.Item, itemDetail)
        logItemEvent({
            action: ItemAction.Impression,
            item: itemDetail,
            ...appData
        }, catalogData)


    }

    CfCore.getInstance().startTrackingImpressions(impressionHandler, containerClassname, itemClassname, searchId)
}

/**
 * When the container indicated in `startTrackingImpressions` is no longer
 * available (i.e.: the user has jumped to another section), call this function
 * to stop monitoring those items inside the removed container
 *
 * @param {string} containerClassname The className that identifies the container
 * to stop tracking
 */
const stopTrackingImpressions = (containerClassname) => {
    CfCore.getInstance().stopTrackingImpressions(containerClassname)
}


/**
 * When the search is updated, that is, the user introduced a new query
 * or a new attribute in the available selector, but the container does not
 * changed, call the this function. It may happen that some items are shared
 * with the previous search but, due searchId has changed, they will be already
 * logged
 *
 * @param {string} searchId
 *
 * @throws `unknown-container` when the `containerClassname` was not being already tracked
 */
const restartTrackingImpressions = (containerClassname: string, searchId: string) => {
    CfCore.getInstance().restartTrackingImpressions(containerClassname, searchId)
}

const verifyItemObject = (eventName: ECommerceTypes, itemObject: ItemObject) => {
    if (!itemObject.id) {
        new CfExceptionHandler().throwAndLogError(eventName, 'empty item id')
    } else if (!itemObject.type) {
        new CfExceptionHandler().throwAndLogError(eventName, 'invalid item type')
    } else if (itemObject.quantity < 0) {
        new CfExceptionHandler().throwAndLogError(eventName, 'invalid item quantity')
    } else if (itemObject.price < 0) {
        new CfExceptionHandler().throwAndLogError(eventName, 'invalid item price')
    } else if (!itemObject.currency) {
        new CfExceptionHandler().throwAndLogError(eventName, 'item currency required')
    }

    if (itemObject.type === ItemType.Subscription) {
        if (!itemObject.subscription) {
            new CfExceptionHandler().throwAndLogError(eventName, 'invalid subscription detail object')
        } else if (!itemObject.subscription.type) {
            new CfExceptionHandler().throwAndLogError(eventName, 'invalid subscription detail object type')
        } else if (!itemObject.subscription.status) {
            new CfExceptionHandler().throwAndLogError(eventName, 'invalid subscription detail object status')
        } else if (!itemObject.subscription.subscription_items) {
            new CfExceptionHandler().throwAndLogError(eventName, 'invalid subscription detail object list')
        } else if (itemObject.subscription.subscription_items.length == 0) {
            new CfExceptionHandler().throwAndLogError(eventName, 'subscription items list is required')
        }

        itemObject.subscription.subscription_items.every(item => {
            if (!item.id) {
                new CfExceptionHandler().throwAndLogError(NavigationTypes.Search, 'empty subscription items list item id')
            } else if (!item.type) {
                new CfExceptionHandler().throwAndLogError(NavigationTypes.Search, 'empty subscription items list item type')
            }
        })
    }

}

/**
 * Private function to inject repositories
 *
 * @ignore
 */
const init = (
    injectedCatalogRepository: ICatalogRepository
) => {
    catalogRepository = injectedCatalogRepository
}

export default {
    updateDrugCatalog,
    updateGroceryCatalog,
    updateFacilityCatalog,
    updateBloodCatalog,
    updateOxygenCatalog,
    updateMedicalEquipmentCatalog,
    logItemReportEvent,
    logItemRequestEvent,
    logItemVerificationEvent,
    logCancelCheckoutEvent,
    logCartEvent,
    logCheckoutEvent,
    logDeliveryEvent,
    logItemEvent,
    restartTrackingImpressions,
    startTrackingImpressions,
    stopTrackingImpressions,
    init
}
