import ECommmercePropertiesTI from '../ECommerce/typings-ti'
import OxygenPropertiesTI from '../ECommerce/oxygen.typings-ti'
import BloodPropertiesTI from '../ECommerce/blood.typings-ti'
import MedicalEquipmentPropertiesTI from '../ECommerce/medicalEquipment.typings-ti'
import NavigationPropertiesTI from './typings-ti'

import {
    AppProperties,
    MediaType,
    ContentBlock,
    IdentifyProperties,
    MediaCatalog,
    MediaProperties,
    NavigationTypes,
    NudgeResponseProperties,
    PageProperties,
    RateProperties,
    ModuleType,
    SearchProperties, RateType, UserCatalog, CoreCatalogType, SiteCatalog, ModuleSelectionProperties
} from './typings'

import {injectEvent} from "../../core/injector"
import {getCoreInstance} from '../../coreInstanceGetter'
import {ICatalogRepository} from '../../core/repositories/catalog/CatalogRepository'
import {ItemMinObject} from '../ECommerce/typings'
import {hasRepeatedIds} from '../../utils'
import CfExceptionHandler from "../../core/CfExceptionHandler";

let catalogRepository: ICatalogRepository;

const moduleName = ContentBlock.Core

/**
 * `title` may be overwritten by the developer
 * This variable stores the known title, that is,
 * the one provided by the CfAppStateMonitor or by developer
 */
let lastKnownTitle

/**
 * @internal
 *
 * @param userId
 * @param properties
 * @param sendNow
 */
const logIdentifyEvent = (properties: IdentifyProperties, sendNow = false, ) => {
    injectEvent(
        properties,
        [NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
        NavigationTypes.Identify,
        moduleName,
        '',
        sendNow)
}

/**
 * @internal
 *
 * @param eventType
 * @param properties
 * @param sendNow
 */

const logIngestEvent = (
    eventType: NavigationTypes,
    properties: AppProperties | PageProperties | MediaProperties | SearchProperties | RateProperties| ModuleSelectionProperties,
    sendNow = false
) => {
    let checkProps: AppProperties | PageProperties | MediaProperties | SearchProperties | RateProperties | ModuleSelectionProperties | null = null;
    switch (eventType) {
        case NavigationTypes.App:
            checkProps = validateAppEvent(properties as AppProperties);
            break;
        case NavigationTypes.Page:
            checkProps = validatePageEvent(properties as PageProperties);
            break;
        case NavigationTypes.Media:
            checkProps = validateMediaEvent(properties as MediaProperties);
            break;
        case NavigationTypes.Search:
            checkProps = validateSearchEvent(properties as SearchProperties);
            break;
        case NavigationTypes.Rate:
            checkProps = validateRateEvent(properties as RateProperties);
            break;
        case NavigationTypes.ModuleSelection:
            checkProps = validateModuleSelectionEvent(properties as ModuleSelectionProperties);
            break;
        default:
            return; // Exit early if eventType is unrecognized
    }

    if (!checkProps) {
        return; // Exit if the properties validation failed
    }

    injectEvent(
        checkProps,
        [NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
        eventType,
        moduleName,
        '',
        sendNow
    );
};

const logCatalogEvent = (coreCatalogType : CoreCatalogType, catalogProps : UserCatalog | MediaCatalog | SiteCatalog) => {

    switch (coreCatalogType) {
        case CoreCatalogType.User:
            catalogRepository.injectUser(catalogProps as UserCatalog)
            break;
        case CoreCatalogType.Site:
            catalogRepository.injectSite(catalogProps as SiteCatalog)
            break;
        case CoreCatalogType.Media:
            catalogRepository.injectMedia(catalogProps as MediaCatalog)
            break;
        case CoreCatalogType.Other:
            break;
    }
}

const validateModuleSelectionEvent = (properties: ModuleSelectionProperties) => {

    if (!properties.type) {
        new CfExceptionHandler().throwAndLogError(NavigationTypes.ModuleSelection, 'module type is required')
        return null
    }
    return properties
}


/**
 * Whenever the user interacts with the App, log that action with this method
 */
const validateAppEvent = (properties: AppProperties) : AppProperties => {
    if(properties.action == undefined){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.App, 'app action is required')
        return null
    }
    return properties
}

/**
 * SDK tracks automatically page changes by inspecting the URL. However,
 * depending on the app implementation, it may happen that the URL does not
 * change when a new view is presented to the user. For these cases, use this method
 */
const validatePageEvent = (properties: PageProperties): PageProperties => {
    if (properties.duration === 0) {
        // avoid noise due internal URLs that changes so fast
        return null
    }
    properties.title = lastKnownTitle ? lastKnownTitle : properties.title
    lastKnownTitle = undefined
    return properties
}


/**
 * Whenever the user interacts with a media element (Video, Audio, Image), log that
 * action with this method
 */
const validateMediaEvent = (properties: MediaProperties) : MediaProperties => {

    properties.time = properties.type === MediaType.Image ? 0 : properties.time

    if(properties.id == ''){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Media, 'empty media id')
        return null
    }else if(properties.type == undefined){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Media, 'media type is required')
        return null
    }else if(properties.action == undefined){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Media, 'media action is required')
        return null
    }else if(properties.time < 0){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Media, 'negative media seek time')
        return null
    }
    return properties

}


/**
 * This function logs into the platform a new search performed by the user,
 * including both, the parameters and the resulting ids
 */
const validateSearchEvent = (properties: SearchProperties): SearchProperties => {

    if(properties.query == ''){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Search, 'empty search query')
        return null
    }else if(properties.page < 0){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Search, 'negative search page')
        return null
    }else if(properties.module){
        properties.module = properties.module ?? ModuleType.Core
    }else if (!properties.results_list) {
        properties.results_list = []
    }

    if (properties.results_list.length !== 0) {
        properties.results_list.every(item => {
            if(item.id == ''){
                new CfExceptionHandler().throwAndLogError(NavigationTypes.Search, 'empty search result item id')
                return null
            }else if(item.type == undefined){
                new CfExceptionHandler().throwAndLogError(NavigationTypes.Search, 'empty search result item type')
                return null
            }
        })
        properties.results_list = properties.results_list as ItemMinObject[]
    }

    if (hasRepeatedIds(properties.results_list)) {
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Search, 'search result contains repeated ids')
        return null
    }
    return properties
}

/**
 * This function logs rate value provided by the user
 */
const validateRateEvent = (properties: RateProperties): RateProperties => {
    const RATE_MIN_VALUE = 0
    const RATE_MAX_VALUE = 5

    if(properties.rate_value < 0){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Rate, 'negative rate value')
        return null
    }else if (properties.rate_value < RATE_MIN_VALUE || properties.rate_value > RATE_MAX_VALUE) {
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Rate, 'invalid rate range 0-5')
        return null
    }else if(properties.type == undefined){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Rate, 'rate type is required')
        return null
    }else if(properties.subject_id == undefined || properties.subject_id == ''){
        new CfExceptionHandler().throwAndLogError(NavigationTypes.Rate, 'rate subject id is required')
        return null
    }

    return properties
}



/**
 * @internal
 *
 * @param properties
 * @param sendNow
 */
const logPushNotificationEvent = (properties: NudgeResponseProperties, sendNow = true): void => {
    injectEvent(
        properties,
        [NavigationPropertiesTI, ECommmercePropertiesTI, BloodPropertiesTI, OxygenPropertiesTI, MedicalEquipmentPropertiesTI],
        NavigationTypes.NudgeResponse,
        moduleName,
        '',
        sendNow)
}




/**
 * SDK has some autonomy to trigger events automatically. For instance,
 * it detects URL changes and notify causalfoundry.ai backend about that changes
 * and the duration. However, it is not able to find out which block that URL
 * belongs to. The goal of this method is to solve that. So, call it whenever
 * the user goes to another section within the site. For example, if the user
 * jumps from the e-commerce section to e-learning, call in this way:
 *
 * ```
 * Navigation.setCurrentBlock(ContentBlock.Elearning)
 * ```
 *
 * @param {ContentBlock} block the new section where the user has just jumped
 */
const setCurrentBlock = (block: ContentBlock) => {
    const core = getCoreInstance()

    core.setCurrentBlock(block)
}

/**
 * There are scenarious where the browser page title is not available,
 * for instance when the web is embedded within a webview. Use this function
 * whenever the new page is loaded to let the SDK know the title.
 *
 * Note that only is necessary when the standard `document.title` is not
 * available for any reason
 *
 *
 * @param title
 */
const setTitle = (title: string) => {
    // the timeout is for waiting for the page-changed event, if exists,
    // see CfSystem for more details
    setTimeout(() => {
        lastKnownTitle = title
    }, 15)
}


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


export default {
    logIdentifyEvent,
    logIngestEvent,
    logCatalogEvent,
    logPushNotificationEvent,
    setCurrentBlock,
    setTitle,
    init
}
