// Angular Core
import { Injectable, Renderer2, RendererFactory2, EventEmitter} from '@angular/core';
import { HttpClient } from '@angular/common/http';

// Services
import { ApiService } from '../api/api.service';
import { LocalStorageService } from '../localstorage/local-storage.service';
import { IsobjectPipe } from 'src/app/filters/is-object.pipe';
import { IsArrayPipe } from 'src/app/filters/is-array.pipe';
import { FormGroup } from '@angular/forms';
import { NgbDate, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';

@Injectable({
  providedIn: 'root'
})
export class UtilitiesService {
  authStateLoadEvent = new EventEmitter<any>();

  private renderer: Renderer2;

  public dateFormat!: String;
  private checkoutData: any;

  constructor(private http: HttpClient, public api: ApiService, private localStorageService: LocalStorageService, private rendererFactory: RendererFactory2)
 {
    this.renderer = this.rendererFactory.createRenderer(null, null);
 }


  /**
   * replaces all matches of the searched string
   * @param {string} string
   * @param {string} search
   * @param {string} replacement
   * @returns {string}
   */
  replaceAll(string: string, search: string, replacement: string) {
    return string.replace(new RegExp(search, 'g'), replacement);
    return string;
  };

  /**
   * Retrieve Supported file types.
   */
  getAcceptedFileTypes() {
    return ['pdf', 'png', 'jpg', 'jpeg', 'tiff', 'pict', 'rtf'];
  };

  /**
  * Returns Mime Type for a given extension.
  * @param {String} fileExt
  * @returns {String}
  */
  getFileType(fileExt: string) {
    let file_type:string = '';
    switch (fileExt) {

      case 'jpg':
      case 'jpeg':
        file_type =  'image/jpeg';
        break;

      case 'rtf':
        file_type =  'application/rtf';
        break;
        
      case 'png':
        file_type =  'image/png';
        break;
      
      case 'pdf':
        file_type =  'application/pdf';
        break;
      
      case 'tif':
      case 'tiff':
        file_type =  'image/tiff';
        break;

      case 'pict':
      case 'pic':
      case 'pct':
        file_type =  'octet/stream';
        break;

      default:
        break;
    }
    return file_type;
  };

  /**
   * Remove an element from array basing on value supplied.
   * 
   * @param {number[]} array
   * @param {number} value
   * @returns {unresolved}
   */
  removeItemByValue(array:number[], value:number) {
    return array.filter(function (v) {
        return value !== v;
    });
  };

  /**
   * Returns short date filter values that must be applied on
   * search when user opts to hide short dates on site preferences section
   * @returns {Array}
   */
  getStickyFilters() {
    var shortDateFilters = [1, 2, 3, 4];
    var filterPreferenceSet = false;
    if (this.localStorageService.getItem('hide_exp_date_0_3_months') === 'true') {
        shortDateFilters = this.removeItemByValue(shortDateFilters, 1);
        filterPreferenceSet = true;
    }
    if (this.localStorageService.getItem('hide_exp_date_4_9_months') === 'true') {
        shortDateFilters = this.removeItemByValue(shortDateFilters, 2);
        filterPreferenceSet = true;
    }
    if (this.localStorageService.getItem('hide_exp_date_10_12_months') === 'true') {
        shortDateFilters = this.removeItemByValue(shortDateFilters, 3);
        filterPreferenceSet = true;
    }
    if (this.localStorageService.getItem('hide_exp_date_12_months') === 'true') {
        shortDateFilters = [1, 2, 3];
        filterPreferenceSet = true;
    }
    if (!filterPreferenceSet) {
        shortDateFilters = [];
    }
    
    return shortDateFilters;
  };

  /**
   * Return day code for the valid date string.
   * 0-6 for Sun - Sat days respectively.
   * @param {String} date
   * @returns {Number}
   */
  getDay(date: string) {
    return new Date(date).getDay();
  };

  /**
   * Decides if 'FRI' must be displayed for before
   * cut off time.
   * @param {String} purchaseDate
   * @returns {String}
   */
  showDay(purchaseDate: string) {
    var day = '';
    //0-> Sunday, 6 -> Saturday
    if ([0, 6].indexOf(this.getDay(purchaseDate)) !== -1) {
        day = 'FRI';
    }
    
    return day;
  };

  /**
   * Merge source object to destination.
   * @param destination
   * @param source
   * @returns any
   */
  merge(destination: any, source: any): any {
    let keys = Object.keys(source);
    let isObject = new IsobjectPipe();
    let isArray = new IsArrayPipe();
    for (let j = 0, jj = keys.length; j < jj; j++) {
      let key = keys[j];
      let val = source[key];
      if (isObject.transform(val)) {
        if (key !== '__proto__') {
          if (!isObject.transform(destination[key])) destination[key] = isArray.transform(val) ? [] : {};
          this.merge(destination[key], val);
        }
      } else {
        destination[key] = val;
      }
    }
    return destination;
  }

  /**
   * Checks if given number 
   * @param value 
   * @returns boolean
   */
  isNumber(value: any): boolean {
    return typeof value === 'number';
  }
  
  /**
   * Validates the date when entered Manually.
   * @param dateInput
   * @returns validatedData
   */
  validateManualDate(dateInput: any): any {
    var rawDate = dateInput.srcElement.value || '';
    var parsedDate = new Date(rawDate);
    var year = parsedDate.getFullYear();
    var isValid = (
      rawDate !== '' &&
      !isNaN(parsedDate.getTime()) &&
      year.toString().length === 4 &&
      rawDate.length === 10
    );
    var today = new Date();
    today.setHours(0, 0, 0, 0);
    var dateStruct = new NgbDate(year, parsedDate.getMonth() + 1, parsedDate.getDate());
    var validatedData = {
      valid: isValid,
      dateString: rawDate,
      inputDate: parsedDate,
      currentDate: today,
      ngbDate: dateStruct
    }
    return validatedData;
  }

  /**
   * Convert Angular Reactive FormData object to plain javascript
   * key value pair object.
   * @param form FormData
   * @returns Object
   */
  convertFormToModelObj (form: FormGroup): Object {
    interface obj {[key: string]: any};
    let formData: obj = {};
    for (const field in form.controls) {
      formData[field] = form.controls[field].value; 
    }
    return formData;
  }

    /**
     * Method to check if the iframe with the ID 'launcher' has fully loaded and modify its contents.
     * This function waits for the iframe to load and then injects custom CSS styles into its content.
     * If the iframe is not yet fully loaded, it retries after 100ms.
     *
     * @returns {void}
     */
    updateLiveChat(): void {
        setTimeout(() => {
            const iframe = document.getElementById('launcher') as HTMLIFrameElement;

            if (iframe && iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
                const iframeDocument = iframe.contentDocument;

                const styleElement = this.renderer.createElement('style');
                const cssText = this.renderer.createText('.fnTYqJ path { fill: white !important; }');
                this.renderer.appendChild(styleElement, cssText);
                this.renderer.appendChild(iframeDocument.head, styleElement);

                const secondIframe = iframe.parentElement
                    ?.querySelector('div')
                    ?.querySelector('div')
                    ?.querySelector('iframe:nth-child(2)') as HTMLIFrameElement;
                if (secondIframe && secondIframe.contentDocument) {
                    const secondIframeBody = secondIframe.contentDocument.body;

                    if (secondIframeBody) {
                        const targetElement = secondIframeBody.querySelector('button');
                        if (targetElement) {
                            this.renderer.setStyle(targetElement, 'color', 'white');
                            this.renderer.setStyle(targetElement, 'background', '#F69222'); //primeRxOrange(#F69222)
                        }
                    }
                }
            } else {
                this.updateLiveChat();
            }
        }, 100);
    }

    /**
     * Compare two objects and return the differences. Returns the key value
     * pairs that are modified, added and removed from o1(Object 1) when
     * compared to o2 (Object 2)
     * @param o1 Object that needs to be compared
     * @param o2 Base object to compare against
     * @returns Object
     */
    diffHelper(o1: any, o2: any): Object {
        const result = {};
  
        // Iterate through all keys in the first object
        for (const key in o1) {
          if (!o2.hasOwnProperty(key)) {
            // Key is missing in the second object
            (result as any)[key] = o1[key];
          } else if (typeof o1[key] === 'object' && o1[key] !== null && typeof o2[key] === 'object' && o2[key] !== null) {
            // Recursive diff for nested objects
            const nestedDiff = this.diffHelper(o1[key], o2[key]);
            if (Object.keys(nestedDiff).length > 0) {
              (result as any)[key] = nestedDiff;
            }
          } else if (o1[key] !== o2[key]) {
            // Key exists in both but has different values
            (result as any)[key] = o1[key];
          }
        }
  
        // Check for keys that are in the second object but not in the first
        for (const key in o2) {
          if (!o1.hasOwnProperty(key)) {
            (result as any)[key] = o2[key];
          }
        }
  
        return result;
      }

    /**
     * Method to get the Account Status array
     * which contains id and status text
     * @returns array
    */
    getAccountStatuses()
    {
        return {
            '13' : 'APPLY NOW',
            '28' : 'Updated App Req',
            '29' : 'Update Pmt Info'
        };
    }

    setCheckoutData(checkoutData: any, orderIds: any, surchargeAmt:any, paymentMethodInfo: any) {
        this.checkoutData = {
          'suppItemsData' : checkoutData,
          'orderIds' : orderIds,
          'surchargeAmt' : surchargeAmt,
          'paymentMethodInfo' : paymentMethodInfo
        }
    }

    getCheckoutData() {
        return this.checkoutData;
    }

    /**
     * Calculating totals for each supplier
     * @param {type} supplier
     * @return {total|fee}
     */
    getSuppOrderTotal(supplier: any) {
        var fee = Number(supplier.shippingData.shippingFee);
        var additionalAmt = Number(supplier.supplierData.additional_charge_amt);

        var supplierTotal = fee + additionalAmt + supplier.totalOrderAmount;
        return supplierTotal;
    };

    /**
     * Calculating complete items total amount for standard suppliers
     * @param object orderData
     * @return {Number}
     */
    getStandardSuppTotal(orderData: any) {
        var itemTotal = 0;
        const suppInfo =  Object.keys(orderData).map(key => ({key, value: orderData[key]}));
        suppInfo.forEach((supplier: any) => {
          const suppItems =  Object.keys(supplier.value.items).map(key => ({key, value: supplier.value.items[key]}));
          suppItems.forEach((suppItem: any) => {
            if (!supplier.value.is_supp_payment_acc_exists) {
                var formatted_total_price = suppItem.value?.total_price.toString().split(",").join("")
                itemTotal += Number(formatted_total_price);
            }
          })
        })
        return itemTotal;
    };

    /**
     * Calculating complete shipping fee amount for cart
     * @param {type} orderData
     * @return {Number}
     */
    getShipphingFeeTotal(orderData: any) {
        var feeTotal = 0;
        const suppInfo =  Object.keys(orderData).map(key => ({key, value: orderData[key]}));

        suppInfo.forEach((supplier: any) => {
          if (!supplier.value.is_supp_payment_acc_exists) {
            if(!supplier.value.shippingData.hasOwnProperty('shippingFee')) {
                supplier.value.shippingData.shippingFee = 0;
            }
            feeTotal += Number(supplier.value.shippingData.shippingFee);
          }
        })
        return feeTotal;
    };
    
    /**
     * Method to check if the Expiration Date is below 90 days or not
     * @param {string} expireDate
     * @return {Boolean}
     */
    getExpireDateBelow90Days(expireDate: string) {
        const exp = new Date(expireDate).getTime();
        const today = Date.now();
        const diffInDays = Math.floor((exp - today) / (1000 * 60 * 60 * 24));
        return diffInDays <= 90 && diffInDays >= 0;
    }

    /**
     * Method to calculate order subtotal for each payment setup enabled supplier
     * @param supplier
     * @return number
    */
    getSuppSubTotal(supplier: any): number{
        var TotalAmt = 0;
        const suppItems =  Object.keys(supplier.items).map(key => ({key, value: supplier.items[key]}));

        suppItems.forEach((item: any) => {
          var formatted_total_price = item.value?.total_price.toString().split(",").join("")
          TotalAmt += Number(formatted_total_price);
        })
        return TotalAmt;
    };
    
    /**
     * Parses a date string in `YYYY-MM-DD` format into a local `Date` object.
     *
     * Returns `null` if the date string is empty, invalid, or represents
     * a zero date (`0000-00-00`).
     *
     * @param dateString - Date string in `YYYY-MM-DD` format
     * @returns A local `Date` instance or `null` if parsing fails
     */
    parseLocalDate(dateString: string): Date | null {
        if (!dateString || dateString === '0000-00-00') {
            return null;
        }

        const [year, month, day] = dateString.split('-').map(Number);

        // Month is 0-based in JS Date
        return new Date(year, month - 1, day);
    }

    /**
     * Records home tab clicks
    */
    recordTabClicks(tabName: string) {
        let data = {
          'tabName': tabName
        };
        return this.api.create('/User-Suggestive/recordTabClicks', data);
    }

    /**
    * Handles API error
    * @param {Object} response
    * @returns {undefined}
    */
    errorCallBack(response: any) {
        this.api.errorCallback(response);
    };
}
