import type { FiltersState } from '@assets/filters/state';
import type {
   FloatParameter,
   Label,
   ListParameter,
   VariationLabelChoice,
} from '@assets/filters/common/state-parsers/filterValuesCache';
import type { Range } from '@assets/filters/common/components/RangeFilter';
import cookies from 'js-cookie';
import {
   IGraphQLAutocompleteProductResultItem,
   IGraphQLAutocompleteResult
} from '@assets/interfaces/gqlApi/graphql';

type Parameter = { id: number; param_name: string }
   & Partial<Omit<FiltersState['config']['parameters'][number], 'values'>>;

type ParameterValue = ListParameter['values'][number] | FloatParameter['values'];

type VariationLabel = { name: string, id: number };
type VariationChoice = VariationLabelChoice & { id: number };

type AutocompleteResults = {
   products?: IGraphQLAutocompleteResult,
   sections?: IGraphQLAutocompleteResult,
   producers?: IGraphQLAutocompleteResult,
   pages?: IGraphQLAutocompleteResult,
   articles?: IGraphQLAutocompleteResult,
};

export class DataCollector {
   private static defaultInstance: DataCollector | null = null;
   private productsCache: Record<string, any>[] = [];

   static default() {
      if (!DataCollector.defaultInstance) {
         DataCollector.defaultInstance = new DataCollector();
      }

      return DataCollector.defaultInstance;
   }

   applyFilterParams(
      parameter: Parameter,
      parameterValue: ParameterValue,
      active: boolean,
   ) {
      let filtrValue: Record<string, any> = {
         id: '',
         name: '',
         min: '',
         max: '',
         value: '',
         active,
      };

      if ('min' in parameterValue && 'max' in parameterValue) {
         filtrValue = {
            ...filtrValue,
            min: parameterValue.min,
            max: parameterValue.max,
         };
      } else {
         filtrValue = {
            ...filtrValue,
            id: parameterValue.id,
            name: parameterValue.name,
         };
      }

      const dataContainer = {
         event: 'apply_filter_params',
         filter_id: parameter.id,
         filter_name: parameter.param_name,
         filter_value: filtrValue
      };

      this.doPush(null, dataContainer);

      return dataContainer;
   }

   /**
    * @param title the text shown to user
    * @param sort the get parameter that is used on backend (value of the select / radio button)
    */
   applyFilterSort(
      title: string,
      sort: string|number,
   ) {
      const dataContainer = {
         event: 'apply_filter_sort',
         filter_value: {
            id: sort,
            name: title,
         },
      };

      this.doPush(null, dataContainer);

      return dataContainer;
   }

   applyFilterVariations(
      label: VariationLabel,
      choice: VariationChoice,
      active: boolean,
   ) {
      const filtrValue = {
         id: choice.id,
         name: choice.name,
         active: active,
      };

      const dataContainer = {
         event: 'apply_filter_variations',
         filter_id: label.id,
         filter_name: label.name,
         filter_value: filtrValue,
      };

      this.doPush(null, dataContainer);

      return dataContainer;
   }

   applyFilterLabels(label: Label, active: boolean) {
      const dataContainer = {
         event: 'apply_filter_labels',
         filter_value: {
            id: label.id,
            name: label.name,
         },
         active,
      };

      this.doPush(null, dataContainer);

      return dataContainer;
   }

   applyFilterPrice(
      value: Range,
      filterConfig: FiltersState['config']['priceRange'],
      active: boolean,
   ) {
      const dataContainer = {
         event: 'apply_filter_price',
         filter_min: filterConfig.from,
         filter_max: filterConfig.to,
         filter_has_histogram: !!filterConfig.histogram,
         filter_value: {
            min: value.from,
            max: value.to,
         },
         active,
      };

      this.doPush(null,dataContainer);

      return dataContainer;
   }

   applyFilterInStore(
      inStoreValue: string,
      title: string,
      active: boolean,
   ) {
      const dataContainer = {
         event: 'apply_filter_in_store',
         filter_value: {
            key: inStoreValue,
            title,
         },
         active,
      };

      this.doPush(null, dataContainer);

      return dataContainer;
   }

   pushUserInfo() {
      const userInfo = {
         event: 'user_info',
         user: { type : 'anonymous' ,
            cart_id : 'no-cartID',
            session_id: 'no-cartID',
         }
      }

      let userData = cookies.get('jsShopGtmUser');
      let userDataParsed = null;

      if(userData) {
        userDataParsed = JSON.parse(userData);
        cookies.remove('jsShopGtmUser');
      } else {
         userData = localStorage.getItem('js-shop-gtm-user');
         if(userData) {
            userDataParsed = JSON.parse(userData).data;
         }
      }

      if (userDataParsed) {
         userInfo.user = {
            ...userInfo.user,
            ...userDataParsed,
         }
      }

      this.doPush(null, userInfo);
   }

   doPush(element: Element|null, ...rawData: (string|object)[]) {
      const data: object[] = rawData.map((item) => {
         let dataAsObject: object;
         if (typeof item === 'string') {
            dataAsObject = JSON.parse(item);
         } else {
            dataAsObject = item;
         }

         return this.processData(element, dataAsObject);
      });

      window.dataLayer.push(...data);
   }

   private processData(element: Element|null, dataObject: Record<string, any>) {
      dataObject = this.replaceProducts(element, dataObject);

      let uid = +(cookies.get('gtm_event_uid') || 1);
      dataObject.event_id = ++uid;

      cookies.set('gtm_event_uid', uid.toString(), { expires: 2592000 });

      if (dataObject.hasOwnProperty('ecommerce')) {
         window.dataLayer.push({ 'ecommerce': null});
      }

      return dataObject;
   }

   private replaceProducts(element: Element|null, dataObject: Record<string, any>): object {
      let me = this;

      for (let key in dataObject) {
         if (dataObject.hasOwnProperty(key)) {
            if (typeof dataObject[key] === 'string' && key === 'items' && dataObject[key] == '#products#') {
               dataObject[key] = this.findInsideProducts(element);
            }

            if (typeof dataObject[key] === 'object') {
               if (key === 'items') {
                  let products = this.fillProducts(element, dataObject[key]);
                  dataObject[key] = this.replaceProductsPlaceholders(products, dataObject);
               } else {
                  dataObject[key] = me.replaceProducts(element, dataObject[key]);
               }
            }
         }
      }

      return dataObject;
   }

   findInsideProducts(element: Element|null): Object[] {
       let values: Object[] = [],
       position: number = 1;

       for (const el of Array.from(element.children)) {
           if (el instanceof HTMLDivElement) {
                const value = el.getAttribute('data-tracking-click');
                if (value) {
                   let clickEvent = JSON.parse(value);
                   if (clickEvent.ecommerce.items) {
                      clickEvent.ecommerce.items[0]['position'] = position++;
                      values.push(...clickEvent.ecommerce.items);
                   }
                } else {
                   if (el.children) {
                      values.push(...this.findInsideProducts(el));
                   }
                }
          }
       }

       return values;
   }

   whisperSearch(searchTerm: string, results: AutocompleteResults) {
      const dataContainer = {
         event: 'whisper_search',
         search_term: searchTerm,
         results: this.parseAutocompleteResults(results),
      };

      this.doPush(null, dataContainer);

      return dataContainer;
   }

   private parseAutocompleteResults(results: AutocompleteResults) {
      const returnObject = {};

      returnObject['products'] = (results.products?.items as IGraphQLAutocompleteProductResultItem[]).map((product) => ({
         id: product.id ?? null,
         name: product.title ?? null,
         price: product.price ?? null,
         brand: null,
      }));

      returnObject['articles'] = results.articles?.items.map((article) => ({
         id: article.id ?? null,
         label: article.title ?? null,
         image: article.photo ?? null,
      }));

      returnObject['categories'] = results.sections?.items.map((section) => ({
         id: section.id ?? null,
         name: section.title ?? null,
         path: section.url ?? null,
         image_url: section.photo ?? null,
      }));

      returnObject['producers'] = results.producers?.items.map((producer) => ({
         id: producer.id ?? null,
         name: producer.title ?? null,
      }));

      returnObject['pages'] = results.pages?.items.map((page) => ({
         id: page.id ?? null,
         name: page.title ?? null,
         path: page.url ?? null,
      }));

      return returnObject;
   }

   private fillProducts(element: Element|null, products: Record<string, any>) {
      const productsObjects: object[] = [];

      for (const key in products) {
         if (Object.prototype.hasOwnProperty.call(products, key)) {
            if (Object.prototype.hasOwnProperty.call(products[key], 'item_id') &&  products[key]['item_id'].startsWith('*')) {
                  const id = products[key]['item_id'].substring(1);

                  delete products[key]['item_id'];
                  // Přehozeno aby to načtené z cache se přerazilo tím co je připravené přímo v datech z data atributu
                  const productObject = {...this.getProductFromCache(products[key], id), ...products[key]};

                  if ((!Object.prototype.hasOwnProperty.call(productObject, 'position')) && element instanceof HTMLElement) {
                     const parent = element.closest('div[data-tracking-default]');
                     if (parent) {
                          const siblings = Array.from(parent.children);
                          const condition = siblings.length > 2;
                          productObject.position = (
                              condition
                              ? siblings.indexOf(element)
                              : Array.from(parent.querySelectorAll('div[data-tracking-click]')).indexOf(element)) + 1;
                      }
                  }

                  productsObjects.push(productObject);
            }
         }
      }

      return productsObjects;
   }

   private getProductFromCache(element: any[], key: string): any {
      const me = this;
      if (!me.productsCache.hasOwnProperty(key)) {
         const scripts = document.querySelectorAll('script[type="wpj/gtm"]');

         scripts.forEach(function(json) {
            me.productsCache = {...me.productsCache, ...JSON.parse(json.textContent)};
         });
      }

      if (this.productsCache.hasOwnProperty(key)) {
         return this.productsCache[key];
      }

      return element;
   }

   private replaceProductsPlaceholders(products: object, dataObject: object): object {
      for (const [id, product] of Object.entries(products)) {
         for (const [k, value] of Object.entries(product)) {
            if ((typeof value == 'string') && value.startsWith('@')) {
               let attrName = value.substring(1);
               if (dataObject.hasOwnProperty(attrName)) {
                  product[k] = dataObject[attrName];
               } else {
                  product[k] = null;
               }
            }
         }
      }

      return products;
   }

}
